Crash while reloading scripts because "no serializer available"
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 7.8k
- Forks
- 1.2k
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 49
Description
Release Type: Official Release
Version: 4.4 beta 4
Platform(s): Windows
Describe the bug
When I reload scripts after changing them in the IDE, the Game Studio crashes.
To Reproduce
This started when I created a split screen renderer and used it in my graphics compositor.
[DataContract]
public class SplitScreenRenderer : SceneRendererBase
{
/// <summary>
/// Gets or sets the camera.
/// </summary>
/// <value>The camera.</value>
/// <userdoc>The camera to use to render the scene.</userdoc>
public required SceneCameraSlot LeftCamera { get; init; }
public required SceneCameraSlot RightCamera { get; init; }
public required ISceneRenderer ForwardRenderer { get; init; }
public required ISceneRenderer DebugRenderer { get; init; }
[Range(0.0f, 1.0f)]
public float SplitPosition { get; set; } = 0.5f;
[Range(0.0f, 1.0f)]
public float SeparatorWidth { get; set; } = 0f;
public Vector4 SeparatorColor { get; set; } = new Vector4(0, 0, 0, 1);
#nullable disable
private SceneCameraRenderer leftCameraRenderer;
private SceneCameraRenderer rightCameraRenderer;
private RenderTextureSceneRenderer leftRenderTextureSceneRenderer;
private RenderTextureSceneRenderer rightRenderTextureSceneRenderer;
private SceneRendererCollection leftRendererCollection;
private SceneRendererCollection rightRendererCollection;
private SplitScreenCompositeEffect compositeEffect;
#nullable restore
protected override void InitializeCore()
{
compositeEffect = ToLoadAndUnload(new SplitScreenCompositeEffect()); // hooks lifecycle into the renderer
leftRendererCollection = ToLoadAndUnload(new SceneRendererCollection()
{
Children = { ForwardRenderer, DebugRenderer }
});
rightRendererCollection = ToLoadAndUnload(new SceneRendererCollection()
{
Children = { ForwardRenderer, DebugRenderer }
});
Texture leftRenderTexture = Texture.New2D(GraphicsDevice, 1, 1, PixelFormat.R8G8B8A8_UNorm_SRgb, TextureFlags.ShaderResource | TextureFlags.RenderTarget);
Texture rightRenderTexture = Texture.New2D(GraphicsDevice, 1, 1, PixelFormat.R8G8B8A8_UNorm_SRgb, TextureFlags.ShaderResource | TextureFlags.RenderTarget);
leftRenderTextureSceneRenderer = ToLoadAndUnload(new RenderTextureSceneRenderer()
{
RenderTexture = leftRenderTexture,
Child = leftRendererCollection,
});
rightRenderTextureSceneRenderer = ToLoadAndUnload(new RenderTextureSceneRenderer()
{
RenderTexture = rightRenderTexture,
Child = rightRendererCollection,
});
leftCameraRenderer = ToLoadAndUnload(new SceneCameraRenderer()
{
Camera = LeftCamera,
Child = leftRenderTextureSceneRenderer,
});
rightCameraRenderer = ToLoadAndUnload(new SceneCameraRenderer()
{
Camera = RightCamera,
Child = rightRenderTextureSceneRenderer,
});
}
protected override void CollectCore(RenderContext context)
{
leftCameraRenderer.Collect(context);
rightCameraRenderer.Collect(context);
}
protected override void DrawCore(RenderContext context, RenderDrawContext drawContext)
{
// Reallocate offscreen targets matching the output size if needed
// TODO: We currently render twice as many pixels as we need to, since we render the left and right cameras at full resolution.
// We could optimize this by rendering at half resolution, but that requires changes to the shader.
var outputTarget = drawContext.CommandList.RenderTarget;
MaybeReplaceTexture(leftRenderTextureSceneRenderer, GraphicsDevice, outputTarget.Width, outputTarget.Height, outputTarget.Format);
MaybeReplaceTexture(rightRenderTextureSceneRenderer, GraphicsDevice, outputTarget.Width, outputTarget.Height, outputTarget.Format);
leftCameraRenderer.Draw(drawContext);
rightCameraRenderer.Draw(drawContext);
DrawCompositeEffect(drawContext);
}
private void DrawCompositeEffect(RenderDrawContext drawContext)
{
compositeEffect.CameraATexture = leftRenderTextureSceneRenderer.RenderTexture;
compositeEffect.CameraBTexture = rightRenderTextureSceneRenderer.RenderTexture;
compositeEffect.SplitPosition = SplitPosition;
compositeEffect.SeparatorWidth = SeparatorWidth;
compositeEffect.SeparatorColor = SeparatorColor;
compositeEffect.Draw(drawContext);
}
private static void MaybeReplaceTexture(RenderTextureSceneRenderer renderer, GraphicsDevice graphicsDevice, int width, int height, PixelFormat format)
{
var texture = renderer.RenderTexture;
if (texture.Width != width || texture.Height != height || texture.Format != format)
{
texture.Dispose();
renderer.RenderTexture = Texture.New2D(graphicsDevice, width, height, format, TextureFlags.ShaderResource | TextureFlags.RenderTarget);
}
}
}
public class SplitScreenCompositeEffect : ImageEffectShader
{
public SplitScreenCompositeEffect() : base("SplitScreenComposite") { }
#nullable disable
public Texture CameraATexture { get; set; }
public Texture CameraBTexture { get; set; }
public float SplitPosition { get; set; } = 0.5f;
public float SeparatorWidth { get; set; } = 0f;
public Vector4 SeparatorColor { get; set; } = new Vector4(0, 0, 0, 1);
#nullable restore
protected override void UpdateParameters()
{
base.UpdateParameters();
Parameters.Set(SplitScreenCompositeKeys.SplitPosition, SplitPosition);
Parameters.Set(SplitScreenCompositeKeys.SeparatorWidth, SeparatorWidth);
Parameters.Set(SplitScreenCompositeKeys.SeparatorColor, SeparatorColor);
}
protected override void DrawCore(RenderDrawContext context)
{
SetInput(0, CameraATexture);
SetInput(1, CameraBTexture);
base.DrawCore(context);
}
}
namespace GraphicsTest.Compositing
{
shader SplitScreenComposite : ImageEffectShader
{
stage float SplitPosition = 0.5f;
stage float SeparatorWidth = 0.0f;
stage float4 SeparatorColor = float4(0, 0, 0, 1);
stage override float4 Shading()
{
float leftEdge_X = SplitPosition - SeparatorWidth * 0.5f;
float rightEdge_X = SplitPosition + SeparatorWidth * 0.5f;
float2 uv = streams.TexCoord;
float screen_X = uv.x;
float camA_X = saturate(screen_X - (0.5f * leftEdge_X) + 0.5f);
float camB_X = saturate(screen_X + (0.5f * rightEdge_X) - 0.5f);
float4 camA = Texture0.Sample(Sampler, float2(camA_X, uv.y));
float4 camB = Texture1.Sample(Sampler, float2(camB_X, uv.y));
float4 temp = lerp(camA, SeparatorColor, step(leftEdge_X, uv.x));
return lerp(temp, camB, step(rightEdge_X, uv.x));
}
};
}
Expected behavior
Reload scripts should work the same as it did before I created this class.
Log and callstacks
Exception: ArgumentException: No serializer available for type GraphicsTest.Compositing.SplitScreenRenderer
at Stride.Core.Serialization.MemberReuseSerializer`1.Serialize(T& obj, ArchiveMode mode, SerializationStream stream) in /_/sources/core/Stride.Core/Serialization/MemberSerializerGenerated.cs:line 1183
at Stride.Core.DataSerializers.StrideAssetsRendering_GraphicsCompositorAssetSerializer.Serialize(GraphicsCompositorAsset& obj, ArchiveMode mode, SerializationStream stream)
at Stride.Core.Serialization.DataSerializer`1.Serialize(Object& obj, ArchiveMode mode, SerializationStream stream) in /_/sources/core/Stride.Core/Serialization/DataSerializer.cs:line 80
at Stride.Core.Serialization.MemberReuseSerializer`1.SerializeExtended(T& obj, ArchiveMode mode, SerializationStream stream, DataSerializer`1 dataSerializer) in /_/sources/core/Stride.Core/Serialization/MemberSerializerGenerated.cs:line 1429
at Stride.Core.Assets.AssetCloner..ctor(Object value, AssetClonerFlags flags, IEnumerable`1 externalIdentifiables) in /_/sources/assets/Stride.Core.Assets/AssetCloner.cs:line 73
at Stride.Core.Assets.Analysis.AssetDependencyManager.Session_AssetDirtyChanged(AssetItem asset, Boolean oldValue, Boolean newValue) in /_/sources/assets/Stride.Core.Assets/Analysis/AssetDependencyManager.cs:line 519
at Stride.Core.Presentation.ViewModels.DirtiableEditableViewModel.UpdateDirtiness(Boolean value) in /_/sources/presentation/Stride.Core.Presentation/ViewModels/DirtiableEditableViewModel.cs:line 47
at Stride.Core.Presentation.Dirtiables.DirtiableManager.UpdateDirtiables(HashSet`1 dirtiables) in /_/sources/presentation/Stride.Core.Presentation/Dirtiables/DirtiableManager.cs:line 121
at Stride.Core.Presentation.Dirtiables.DirtiableManager.TransactionCompleted(Object sender, TransactionEventArgs e) in /_/sources/presentation/Stride.Core.Presentation/Dirtiables/DirtiableManager.cs:line 138
at Stride.Core.Transactions.TransactionStack.CompleteTransaction(Transaction transaction) in /_/sources/core/Stride.Core.Design/Transactions/TransactionStack.cs:line 202
at Stride.Core.Transactions.TransactionStack.CompleteTransaction(Transaction transaction) in /_/sources/core/Stride.Core.Design/Transactions/TransactionStack.cs:line 134
at Stride.Core.Transactions.Transaction.Dispose() in /_/sources/core/Stride.Core.Design/Transactions/Transaction.cs:line 45
at Stride.GameStudio.ViewModels.DebuggingViewModel.ReloadAssemblies() in /_/sources/editor/Stride.GameStudio/ViewModels/DebuggingViewModel.cs:line 397
at Stride.GameStudio.ViewModels.DebuggingViewModel.ReloadAssemblies()
at Stride.Core.Presentation.Commands.AnonymousTaskCommand.<>c__DisplayClass0_0.<<-ctor>b__0>d.MoveNext() in /_/sources/presentation/Stride.Core.Presentation/Commands/AnonymousCommand.cs:line 84
--- End of stack trace from previous location ---
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__124_0(Object state)
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
Additional context
This happens every time I try to reload scripts, not just the first time.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the reload path in Stride.GameStudio/ViewModels/DebuggingViewModel.cs and follow the stack into AssetCloner, AssetDependencyManager, and the serialization classes. Reproduce the failure using the SplitScreenRenderer example and inspect why its type has no serializer. Done means reloading scripts no longer crashes and the reported graphics compositor asset remains usable.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- devtools, game-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100