Hourglass.Workflow.CommandAccumulator (hourglass v0.1.0)

Copy Markdown View Source

Process-dict-keyed mutable accumulator that the pure-function evaluator uses while re-executing a workflow body.

The body runs inline on the evaluator process; the dict carries:

  • :hourglass_temporal_commands_acc — newest-first list of {command_id, command_term} issued during this activation.
  • :hourglass_temporal_child_index_path — the lex-path identifying the currently-active scope ([] for the main body; [0], [1], ... for direct async children; [1, 0] etc. for nested asyncs).
  • :hourglass_temporal_next_child_index — local counter for the next async child's path component within the current scope.
  • :hourglass_temporal_local_seq — local monotonic counter for the current scope; bumped by every next_command_id/0.

Summary

Functions

Append {command_id, command_term} to the per-process command list (newest-first).

Wipe all accumulator keys from the calling process's dict. Called on the evaluator's exit boundary so a re-used process doesn't leak state.

Push a new async-child scope onto the path stack. Returns a frame that must be passed to exit_async_child/1 to restore the parent's scope.

Read the evaluator state from the calling process's dict, or nil when no evaluator is active.

Pop a child scope frame (pushed by enter_async_child/0) and restore the parent scope's path / counters. The parent's next_child is bumped so the next sibling gets a distinct path component.

Initialise the dict for the main workflow body. Wipes any prior state on the calling process.

Mark that the evaluator is currently active on this process. The Workflow API primitives consult this state to resolve commands.

Allocate the next deterministic command_id {path, local_seq} for the current scope. Bumps the scope's local_seq.

Per-execution Nth-call index for await_signal(name); resets each activation via init/0.

Take the accumulated commands (newest-first) and clear the list.

Clear the evaluator-active marker.

Functions

append_command(command_id, command_term)

Append {command_id, command_term} to the per-process command list (newest-first).

clear()

@spec clear() :: :ok

Wipe all accumulator keys from the calling process's dict. Called on the evaluator's exit boundary so a re-used process doesn't leak state.

enter_async_child()

@spec enter_async_child() :: %{
  path: [non_neg_integer()],
  next_child: non_neg_integer(),
  local_seq: non_neg_integer()
}

Push a new async-child scope onto the path stack. Returns a frame that must be passed to exit_async_child/1 to restore the parent's scope.

This is the inline equivalent of spawning a child Task: the child body runs on the same BEAM process but with a child-scoped dict.

evaluator_state()

@spec evaluator_state() :: Hourglass.Workflow.State.t() | nil

Read the evaluator state from the calling process's dict, or nil when no evaluator is active.

exit_async_child(map)

@spec exit_async_child(%{
  path: [non_neg_integer()],
  next_child: non_neg_integer(),
  local_seq: non_neg_integer()
}) :: :ok

Pop a child scope frame (pushed by enter_async_child/0) and restore the parent scope's path / counters. The parent's next_child is bumped so the next sibling gets a distinct path component.

init()

@spec init() :: :ok

Initialise the dict for the main workflow body. Wipes any prior state on the calling process.

mark_evaluator_active(state)

@spec mark_evaluator_active(Hourglass.Workflow.State.t()) :: :ok

Mark that the evaluator is currently active on this process. The Workflow API primitives consult this state to resolve commands.

next_command_id()

@spec next_command_id() :: Hourglass.Workflow.State.command_id()

Allocate the next deterministic command_id {path, local_seq} for the current scope. Bumps the scope's local_seq.

next_signal_index(name)

@spec next_signal_index(String.t()) :: non_neg_integer()

Per-execution Nth-call index for await_signal(name); resets each activation via init/0.

take_commands()

@spec take_commands() :: [Hourglass.Workflow.State.command_entry()]

Take the accumulated commands (newest-first) and clear the list.

unmark_evaluator()

@spec unmark_evaluator() :: :ok

Clear the evaluator-active marker.