---
source: framec/tests/gdscript_snapshots.rs
expression: "compile_fixture(\"03_persist\", \"gdscript\")"
---
class CounterFrameEvent:
    var _message
    var _parameters

    func _init(message: String, parameters):
        self._message = message
        self._parameters = parameters


class CounterFrameContext:
    var event
    var _return
    var _data
    var _transitioned

    func _init(event: CounterFrameEvent, default_return):
        self.event = event
        self._return = default_return
        self._data = {}
        self._transitioned = false


class CounterCompartment:
    var state
    var state_args
    var state_vars
    var enter_args
    var exit_args
    var forward_event
    var parent_compartment

    func _init(state: String, parent_compartment = null):
        self.state = state
        self.state_args = []
        self.state_vars = {}
        self.enter_args = []
        self.exit_args = []
        self.forward_event = null
        self.parent_compartment = parent_compartment

    func copy() -> CounterCompartment:
        var c = CounterCompartment.new(self.state, self.parent_compartment)
        c.state_args = self.state_args.duplicate()
        c.state_vars = self.state_vars.duplicate()
        c.enter_args = self.enter_args.duplicate()
        c.exit_args = self.exit_args.duplicate()
        c.forward_event = self.forward_event
        return c


class Counter:
    var _state_stack
    var __compartment
    var __next_compartment
    var _context_stack
    var count = 0

    func _init():
        self._state_stack = []
        self._context_stack = []
        self.__compartment = self.__prepareEnter("Counting", [], [])
        self.__next_compartment = null

    static func _create():
        var c = Counter.new()
        var __e = CounterFrameEvent.new("$>", c.__compartment.enter_args)
        var __ctx = CounterFrameContext.new(__e, null)
        c._context_stack.append(__ctx)
        c.__kernel(__e)
        c._context_stack.pop_back()
        return c

    func hsm_chain() -> Dictionary:
        return {
            "Counting": ["Counting"],
        }
    func __prepareEnter(leaf, state_args, enter_args):
        var comp = null
        for name in self.hsm_chain()[leaf]:
            var new_comp = CounterCompartment.new(name)
            new_comp.state_args = state_args.duplicate()
            new_comp.enter_args = enter_args.duplicate()
            new_comp.parent_compartment = comp
            comp = new_comp
        return comp

    func __prepareExit(exit_args):
        var comp = self.__compartment
        while comp != null:
            comp.exit_args = exit_args.duplicate()
            comp = comp.parent_compartment

    func __kernel(__e):
        # Route event to current state
        self.__router(__e)
        # Drain any transitions queued by the handler
        while self.__next_compartment != null:
            var next_compartment = self.__next_compartment
            self.__next_compartment = null
            # Exit the current (leaf) state
            self.__router(CounterFrameEvent.new("<$", self.__compartment.exit_args))
            # Switch to the new compartment
            self.__compartment = next_compartment
            if next_compartment.forward_event == null:
                # No forwarded event — synthesize a fresh $>
                self.__router(CounterFrameEvent.new("$>", self.__compartment.enter_args))
            else:
                if next_compartment.forward_event._message == "$>":
                    # Forwarded event IS $> — dispatch directly so the
                    # destination's $> receives the caller's payload
                    self.__router(next_compartment.forward_event)
                else:
                    # Forwarded event is not $> — initialize the destination
                    # with a fresh $>, then dispatch the forward to it
                    self.__router(CounterFrameEvent.new("$>", self.__compartment.enter_args))
                    self.__router(next_compartment.forward_event)
            next_compartment.forward_event = null
            # Mark all stacked contexts as transitioned
            for ctx in self._context_stack:
                ctx._transitioned = true

    func __router(__e):
        if self.__compartment.state == "Counting":
            self._state_Counting(__e, self.__compartment)

    func __transition(next_compartment):
        self.__next_compartment = next_compartment

    func increment(by: int):
        var __e = CounterFrameEvent.new("increment", [by])
        var __ctx = CounterFrameContext.new(__e, null)
        self._context_stack.append(__ctx)
        self.__kernel(__e)
        return self._context_stack.pop_back()._return

    func value() -> int:
        var __e = CounterFrameEvent.new("value", [])
        var __ctx = CounterFrameContext.new(__e, null)
        __ctx._return = 0
        self._context_stack.append(__ctx)
        self.__kernel(__e)
        return self._context_stack.pop_back()._return

    func _state_Counting(__e, compartment):
        if __e._message == "increment":
            self._s_Counting_hdl_user_increment(__e, compartment)
            return
        if __e._message == "value":
            self._s_Counting_hdl_user_value(__e, compartment)
            return

    func _s_Counting_hdl_user_increment(__e, compartment):
        var by = __e._parameters[0]
        self.count = self.count + by

    func _s_Counting_hdl_user_value(__e, compartment):
        self._context_stack[-1]._return = self.count

    func snapshot() -> PackedByteArray:
        if not self._context_stack.is_empty():
            push_error("E700: system not quiescent")
            return PackedByteArray()
        # Serialize compartment chain iteratively
        var _ser_chain = func(comp):
            var chain = []
            var cur = comp
            while cur != null:
                chain.append(cur)
                cur = cur.parent_compartment
            chain.reverse()
            var result = null
            for c in chain:
                var d = {}
                d["state"] = c.state
                d["state_args"] = c.state_args.duplicate()
                d["state_vars"] = c.state_vars.duplicate()
                d["enter_args"] = c.enter_args.duplicate()
                d["exit_args"] = c.exit_args.duplicate()
                d["parent_compartment"] = result
                result = d
            return result
        var state_data = {}
        state_data["_compartment"] = _ser_chain.call(self.__compartment)
        var stack_arr = []
        for c in self._state_stack:
            stack_arr.append(_ser_chain.call(c))
        state_data["_state_stack"] = stack_arr
        state_data["count"] = self.count
        return var_to_bytes(state_data)

    func restore(data: PackedByteArray):
        var state_data = bytes_to_var(data)
        var _deser_chain = func(d):
            if d == null:
                return null
            # Collect chain into array (child first)
            var chain = []
            var cur = d
            while cur != null:
                chain.append(cur)
                cur = cur.get("parent_compartment", null)
            chain.reverse()
            var result = null
            for cd in chain:
                var comp = CounterCompartment.new(cd["state"])
                comp.state_args = cd.get("state_args", {})
                comp.state_vars = cd.get("state_vars", {})
                comp.enter_args = cd.get("enter_args", {})
                comp.exit_args = cd.get("exit_args", {})
                comp.parent_compartment = result
                result = comp
            return result
        self.__compartment = _deser_chain.call(state_data["_compartment"])
        self.__next_compartment = null
        self._state_stack = []
        for c in state_data.get("_state_stack", []):
            self._state_stack.append(_deser_chain.call(c))
        self._context_stack = []
        self.count = state_data.get("count", null)
