| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280 |
- using Invercargill;
- using Invercargill.DataStructures;
- using Inversion;
- using Astralis;
- namespace Statum {
- public enum ModifierOutcome {
- UPDATED,
- UNCHANGED,
- CLEARED,
- }
- // ------------------------------------------------------------------
- // HeldStates
- // ------------------------------------------------------------------
- public class HeldStates : Object {
- private Dictionary<string, string> slot_keys;
- private StateService state_service;
- internal HeldStates(Dictionary<string, string> slot_keys, StateService state_service) {
- this.slot_keys = slot_keys;
- this.state_service = state_service;
- }
- public T? get<T>(string type_name) {
- string slot_key;
- if (!slot_keys.try_get(type_name, out slot_key)) {
- return null;
- }
- var slot = state_service.get_slot(slot_key);
- if (slot == null || ((!)slot).current_state == null) {
- return null;
- }
- try {
- return GObjectMapping.from_properties_typed<T>(typeof(T), ((!)slot).current_state.public_data);
- } catch (GLib.Error e) {
- return null;
- }
- }
- public Properties? get_properties(string type_name) {
- string slot_key;
- if (!slot_keys.try_get(type_name, out slot_key)) {
- return null;
- }
- var slot = state_service.get_slot(slot_key);
- if (slot == null || ((!)slot).current_state == null) {
- return null;
- }
- return ((!)slot).current_state.public_data;
- }
- }
- // ------------------------------------------------------------------
- // TopicLoader (internal non-generic bridge)
- // ------------------------------------------------------------------
- public interface TopicLoader : Object {
- public abstract async GLib.Object? load_payload(string topic_key) throws GLib.Error;
- }
- // ------------------------------------------------------------------
- // Topic<TPayload>
- // ------------------------------------------------------------------
- public abstract class Topic<TPayload> : Object, TopicLoader {
- public abstract async TPayload load(string topic_key) throws GLib.Error;
- public async GLib.Object? load_payload(string topic_key) throws GLib.Error {
- return (GLib.Object) yield load(topic_key);
- }
- }
- // ------------------------------------------------------------------
- // TopicStateModifier (internal non-generic bridge)
- // ------------------------------------------------------------------
- public interface TopicStateModifier : Object {
- public abstract Type state_type { get; }
- public abstract async ModifierOutcome apply(GLib.Object payload, string slot_key, HeldStates held) throws GLib.Error;
- }
- // ------------------------------------------------------------------
- // StateModifier<TPayload, TState>
- // ------------------------------------------------------------------
- public abstract class StateModifier : Object, TopicStateModifier {
- protected StateService state_service = inject<StateService>();
- public abstract Type state_type { get; }
- public string state_type_name {
- get { return state_type.name(); }
- }
- /**
- * Derive new state from the payload + context.
- *
- * @param payload The trigger payload (cast to your expected type).
- * @param current The current state of the slot, or null if expired.
- * @param held Live lookup of the client's held states.
- * @return New Object → push; same ref as {@code current} → skip; null → clear.
- */
- public abstract GLib.Object? derive(GLib.Object payload, GLib.Object? current, HeldStates held);
- public async ModifierOutcome apply(GLib.Object payload, string slot_key, HeldStates held) throws GLib.Error {
- var slot = state_service.get_slot(slot_key);
- GLib.Object? current = null;
- if (slot != null && ((!)slot).current_state != null) {
- try {
- current = GObjectMapping.from_properties(state_type, ((!)slot).current_state.public_data);
- } catch (GLib.Error e) {
- current = null;
- }
- }
- var result = derive(payload, current, held);
- if (result == null) {
- yield state_service.clear_slot(slot_key);
- return ModifierOutcome.CLEARED;
- }
- if ((void*) result == (void*) current) {
- return ModifierOutcome.UNCHANGED;
- }
- var new_state = new State() {
- type_name = (slot != null && ((!)slot).current_state != null)
- ? ((!)slot).current_state.type_name
- : state_type_name,
- public_data = GObjectMapping.to_properties((!)result),
- private_data = (slot != null && ((!)slot).current_state != null)
- ? ((!)slot).current_state.private_data ?? new PropertyDictionary()
- : new PropertyDictionary()
- };
- yield state_service.update(slot_key, new_state);
- return ModifierOutcome.UPDATED;
- }
- }
- // ------------------------------------------------------------------
- // TopicRegistry
- // ------------------------------------------------------------------
- public class TopicRegistry : Object {
- private StateService state_service = inject<StateService>();
- private Container container = inject<Container>();
- private Dictionary<string, Series<Watcher>> topic_watchers = new Dictionary<string, Series<Watcher>>();
- private Dictionary<string, Type> topic_types = new Dictionary<string, Type>();
- private Dictionary<string, Dictionary<string, Type>> prefix_modifier_types = new Dictionary<string, Dictionary<string, Type>>();
- public void register_topic<TTopic>(string prefix) {
- container.register_transient<TTopic>();
- topic_types[prefix] = typeof(TTopic);
- }
- public void register_modifier<TModifier, TState>(string prefix) {
- container.register_transient<TModifier>();
- var stn = typeof(TState).name();
- Dictionary<string, Type> modifiers;
- if (!prefix_modifier_types.try_get(prefix, out modifiers)) {
- modifiers = new Dictionary<string, Type>();
- prefix_modifier_types[prefix] = modifiers;
- }
- modifiers[stn] = typeof(TModifier);
- }
- public void watch(string topic_key, string slot_key, string slot_type,
- ReadOnlyAssociative<string, HeldSlot> request_held) {
- var held_keys = new Dictionary<string, string>();
- foreach (var kvp in request_held) {
- held_keys[kvp.key] = kvp.value.key;
- }
- var watcher = new Watcher() {
- slot_key = slot_key,
- slot_type = slot_type,
- held_keys = held_keys
- };
- Series<Watcher> watchers;
- if (!topic_watchers.try_get(topic_key, out watchers)) {
- watchers = new Series<Watcher>();
- topic_watchers[topic_key] = watchers;
- }
- watchers.add(watcher);
- }
- public async void trigger(string topic_key) throws GLib.Error {
- // Early exit: no watchers → skip the load entirely.
- Series<Watcher> watchers;
- if (!topic_watchers.try_get(topic_key, out watchers) || watchers == null) {
- return;
- }
- if (((!)watchers).to_array().length == 0) {
- return;
- }
- var prefix = extract_prefix(topic_key);
- Type topic_type;
- if (!topic_types.try_get(prefix, out topic_type)) {
- return;
- }
- var scope = container.create_transient_scope();
- var topic_obj = scope.resolve_type(topic_type);
- if (!(topic_obj is TopicLoader)) {
- return;
- }
- var payload = yield ((TopicLoader)(!)topic_obj).load_payload(topic_key);
- Dictionary<string, Type> modifier_types;
- if (!prefix_modifier_types.try_get(prefix, out modifier_types) || modifier_types == null) {
- return;
- }
- var to_remove = new Series<Watcher>();
- foreach (var watcher in (!)watchers) {
- Type modifier_type;
- if (!((!)modifier_types).try_get(watcher.slot_type, out modifier_type)) {
- continue;
- }
- var held = new HeldStates(watcher.held_keys, state_service);
- try {
- var modifier = (TopicStateModifier) scope.resolve_type(modifier_type);
- var outcome = yield modifier.apply(payload, watcher.slot_key, held);
- if (outcome == ModifierOutcome.CLEARED) {
- to_remove.add(watcher);
- }
- } catch (GLib.Error e) {
- try {
- yield state_service.clear_slot(watcher.slot_key);
- } catch {}
- to_remove.add(watcher);
- }
- }
- foreach (var w in to_remove) {
- ((!)watchers).remove(w);
- }
- }
- public void sweep() {
- foreach (var entry in topic_watchers) {
- var watchers = entry.value;
- var to_remove = new Series<Watcher>();
- foreach (var watcher in watchers) {
- if (state_service.get_slot(watcher.slot_key) == null) {
- to_remove.add(watcher);
- }
- }
- foreach (var w in to_remove) {
- watchers.remove(w);
- }
- }
- }
- private static string extract_prefix(string topic_key) {
- var colon = topic_key.index_of(":");
- return colon >= 0 ? topic_key.substring(0, colon) : topic_key;
- }
- private class Watcher : Object {
- public string slot_key;
- public string slot_type;
- public Dictionary<string, string> held_keys;
- }
- }
- }
|