Add a state observer
Use the generic mechanism in Common
(Domain/Observer/ObservableState.cs).
Reference implementation: NoteState,
NoteService,
ExampleUI. See the Observer section in the
Common README.
Steps for a module <Module> and state <Name>State:
Observer interface —
<Module>/Domain/Observer/I<Name>StateObserver.cs, namespaceGame.Game.<Module>.Domain.Observer. Put only the change events you need:public interface I<Name>StateObserver { void OnSomethingChanged(<type> value); }Make the state observable — extend the base and notify on change:
public class <Name>State : ObservableState<I<Name>StateObserver> { public <type> Value { get; set; } // public setter only for JSON public void Change(<type> value) { Value = value; Notify(observer => observer.OnSomethingChanged(value)); } }Mutate only through such methods so observers fire.
Expose subscription from the service (UI depends on Service, not the repository):
public void Subscribe(I<Name>StateObserver observer) => _repository.GetOne().AddObserver(observer);Route every change through the service so it calls
state.Change(...).The UI observes — implement the interface, subscribe in
_Ready, react in the callback:public partial class <Name>UI : Control, I<Name>StateObserver { public override void _Ready() { ...; _service.Subscribe(this); } public void OnSomethingChanged(<type> value) { /* update nodes */ } }
Critical rules
- Re-subscribe after a load. The central
SaveService.Loadreplaces state objects (Delete()+Update()), so any observer subscribed to the old object is now stale. After a successful load, re-subscribe the UI to the fresh state and re-render — seeSubscribeAndRenderin ExampleUI. - Serialization still works — the observer list is a private field, not serialized.
- No
virtual— the base uses a non-virtualNotify(Action<TObserver>)helper. Level/Node-derived classes can't extendObservableState(single inheritance); they keep a manualList<IObserver>likeGameLeveldoes withILevelObserver.
UI → Level (the other direction)
When the UI must report an intent (navigation, "quit", "back") rather than react to state, the UI is the publisher and the level the listener:
<Module>/UI/Observer/I<Name>UIObserver.cswith the intent methods (e.g.OnQuitRequested).- The UI keeps a manual
List<I<Name>UIObserver>+AddObserverand notifies on the action (it's aNode, so it can't extendObservableState). - The hosting level implements the interface,
ui.AddObserver(this)in_ReadybeforeAddChild, and translates the intent (e.g._gameLevel.OpenLevel(...)). Keeps navigation logic in the level, not the UI. Reference: IExampleUIObserver + ExampleLevel.
Tests
Test that a change notifies subscribers (mock the observer interface, Verify the call)
and that the generic base behaves (add/remove/idempotent) — see
ObservableStateTests
and the Subscribe_* test in
NoteServiceTests.
Then run the verify skill.