stride3d / stride3d/stride

Crash while reloading scripts because "no serializer available"

Open
#3,353 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
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)

Report.txt

Additional context

This happens every time I try to reload scripts, not just the first time.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.