{"record":{"id":"cc944b70ae3d26f0","repo":"stride3d/stride","slug":"cannot-poplayer-when-no-balancing-pushlayer-was-called","errorCode":null,"errorMessage":"Cannot PopLayer when no balancing PushLayer was called","messagePattern":"Cannot PopLayer when no balancing PushLayer was called","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/engine/Stride.Rendering/Rendering/Materials/MaterialGeneratorContext.cs","lineNumber":236,"sourceCode":"            var newLayer = new MaterialBlendLayerContext(this, currentLayerContext, blendMap);\n            if (currentLayerContext != null)\n            {\n                currentLayerContext.Children.Add(newLayer);\n            }\n            currentLayerContext = newLayer;\n        }\n\n        /// <summary>\n        /// Pops the current layer.\n        /// </summary>\n        public void PopLayer()\n        {\n            if (Step != MaterialGeneratorStep.GenerateShader)\n                return;\n\n            if (currentLayerContext == null)\n            {\n                throw new InvalidOperationException(\"Cannot PopLayer when no balancing PushLayer was called\");\n            }\n\n            // If we are poping the last layer, so we can process all layers\n            if (currentLayerContext.Parent == null)\n            {\n                ProcessLayer(currentLayerContext, true);\n            }\n            else\n            {\n                currentLayerContext = currentLayerContext.Parent;\n            }\n        }\n\n        public void AddShaderSource(MaterialShaderStage stage, ShaderSource shaderSource)\n        {\n            EnsureStep(MaterialGeneratorStep.GenerateShader);\n            if (shaderSource == null) throw new ArgumentNullException(nameof(shaderSource));\n            currentLayerContext.GetContextPerStage(stage).ShaderSources.Add(shaderSource);","sourceCodeStart":218,"sourceCodeEnd":254,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Rendering/Rendering/Materials/MaterialGeneratorContext.cs#L218-L254","documentation":"MaterialGeneratorContext.PushLayer/PopLayer must be balanced. PopLayer throws InvalidOperationException when there is no open layer (currentLayerContext == null), meaning a custom visitor called PopLayer without a matching PushLayer.","triggerScenarios":"Calling PopLayer when no layer is currently open — a custom IMaterialDescriptor.Visit calls PopLayer unconditionally, or pushes were skipped due to the Step != GenerateShader early-return while pops still execute.","commonSituations":"Custom material assemblies with unbalanced layer code; asymmetric early returns inside Visit after PushLayer but before PopLayer (or vice versa); exceptions interrupting the push/pop sequence.","solutions":["Make every PopLayer in your Visit matched by a preceding PushLayer on all code paths","Check Step guards: pushes and pops must both run only during MaterialGeneratorStep.GenerateShader","Track layer depth yourself (e.g. a bool/counter) and only pop when a push actually happened","Simplify nested-layer code so each branch pushes and pops symmetrically"],"exampleFix":"// before: pop without push guard\npublic override void Visit(MaterialGeneratorContext context)\n{\n    if (context.Step == MaterialGeneratorStep.GenerateShader && enabled)\n        context.PushLayer(...);\n    context.PopLayer(); // throws when !enabled\n}\n// after: pop only when pushed\npublic override void Visit(MaterialGeneratorContext context)\n{\n    if (context.Step != MaterialGeneratorStep.GenerateShader || !enabled) return;\n    context.PushLayer(...);\n    context.PopLayer();\n}","handlingStrategy":"validation","validationCode":"bool layerOpen = context.CurrentLayer != null;\nif (layerOpen) context.PopLayer();","typeGuard":null,"tryCatchPattern":"try { context.PopLayer(); }\ncatch (InvalidOperationException ex)\n{\n    logger.LogError(ex, \"Unbalanced PopLayer in {Visitor}\", GetType().Name);\n}","preventionTips":["Mirror every PushLayer with exactly one PopLayer in the same Visit branch","Apply the same Step == GenerateShader guard to pushes and pops","Add a debug assertion/logging of layer depth in custom descriptors"],"tags":["material","generator","stack-imbalance","csharp"],"backgroundTag":"invalid-state-transition","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}