{"record":{"id":"1627d74cc4435e1c","repo":"EllanJiang/GameFramework","slug":"fsm-is-invalid-fsmstate","errorCode":null,"errorMessage":"FSM is invalid.","messagePattern":"FSM is invalid\\.","errorType":"exception","errorClass":"GameFrameworkException","httpStatus":null,"severity":"error","filePath":"GameFramework/Fsm/FsmState.cs","lineNumber":78,"sourceCode":"        /// <summary>\n        /// 有限状态机状态销毁时调用。\n        /// </summary>\n        /// <param name=\"fsm\">有限状态机引用。</param>\n        protected internal virtual void OnDestroy(IFsm<T> fsm)\n        {\n        }\n\n        /// <summary>\n        /// 切换当前有限状态机状态。\n        /// </summary>\n        /// <typeparam name=\"TState\">要切换到的有限状态机状态类型。</typeparam>\n        /// <param name=\"fsm\">有限状态机引用。</param>\n        protected void ChangeState<TState>(IFsm<T> fsm) where TState : FsmState<T>\n        {\n            Fsm<T> fsmImplement = (Fsm<T>)fsm;\n            if (fsmImplement == null)\n            {\n                throw new GameFrameworkException(\"FSM is invalid.\");\n            }\n\n            fsmImplement.ChangeState<TState>();\n        }\n\n        /// <summary>\n        /// 切换当前有限状态机状态。\n        /// </summary>\n        /// <param name=\"fsm\">有限状态机引用。</param>\n        /// <param name=\"stateType\">要切换到的有限状态机状态类型。</param>\n        protected void ChangeState(IFsm<T> fsm, Type stateType)\n        {\n            Fsm<T> fsmImplement = (Fsm<T>)fsm;\n            if (fsmImplement == null)\n            {\n                throw new GameFrameworkException(\"FSM is invalid.\");\n            }\n","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/EllanJiang/GameFramework/blob/d0c010b05167c58e92350449d04864a91ca13fd2/GameFramework/Fsm/FsmState.cs#L60-L96","documentation":"FsmState<T>.ChangeState<TState>(IFsm<T> fsm) casts the fsm interface to the concrete Fsm<T> and delegates the transition. The library throws GameFrameworkException(\"FSM is invalid.\") when the cast produces null — i.e. the passed IFsm<T> reference is null.","triggerScenarios":"Calling ChangeState<TState>(null) from inside a state's OnUpdate/OnEnter — the null check after the (Fsm<T>) cast fires.","commonSituations":"Storing the IFsm reference in the state and it was never set; calling ChangeState outside a live FSM context (state not attached to a running FSM); lifecycle ordering bugs where the state fires before FSM injection.","solutions":["Pass the IFsm<T> reference supplied to the state's lifecycle methods (OnInit/OnEnter parameters) rather than a cached possibly-null field","Guard the cached reference with a null check before transitioning","Ensure the state is only used while attached to a running FSM"],"exampleFix":"// before\nprotected override void OnUpdate(IFsm<Player> fsm, float elapseSeconds, float realElapseSeconds)\n{\n    cachedFsm.ChangeState<DeadState>(cachedFsm); // cachedFsm may be null\n}\n// after\nprotected override void OnUpdate(IFsm<Player> fsm, float elapseSeconds, float realElapseSeconds)\n{\n    fsm.ChangeState<DeadState>(fsm); // use the provided reference\n}","handlingStrategy":"validation","validationCode":"if (fsm != null)\n    ChangeState<TState>(fsm);","typeGuard":"static bool IsUsableFsm<T>(IFsm<T> fsm) where T : class => fsm is Fsm<T>;","tryCatchPattern":"try { ChangeState<DeadState>(fsm); }\ncatch (GameFrameworkException ex) { Log.Error(\"ChangeState failed: {0}\", ex.Message); }","preventionTips":["Always use the IFsm<T> argument supplied to state lifecycle callbacks","Avoid caching IFsm references in state fields","Only transition while the state is attached to a running FSM"],"tags":["csharp","fsm","state-machine","null-argument"],"backgroundTag":"invalid-state-transition","analyzedSha":"d0c010b05167c58e92350449d04864a91ca13fd2","analyzedAt":"2026-09-15T13:37:15.352Z","contentChangedAt":"2026-09-15T13:37:15.352Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}