antioch-py 2.0.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of antioch-py might be problematic. Click here for more details.

Files changed (99) hide show
  1. antioch/__init__.py +0 -0
  2. antioch/message.py +87 -0
  3. antioch/module/__init__.py +53 -0
  4. antioch/module/clock.py +62 -0
  5. antioch/module/execution.py +278 -0
  6. antioch/module/input.py +127 -0
  7. antioch/module/module.py +218 -0
  8. antioch/module/node.py +357 -0
  9. antioch/module/token.py +42 -0
  10. antioch/session/__init__.py +150 -0
  11. antioch/session/ark.py +504 -0
  12. antioch/session/asset.py +65 -0
  13. antioch/session/error.py +80 -0
  14. antioch/session/record.py +158 -0
  15. antioch/session/scene.py +1521 -0
  16. antioch/session/session.py +220 -0
  17. antioch/session/task.py +323 -0
  18. antioch/session/views/__init__.py +40 -0
  19. antioch/session/views/animation.py +189 -0
  20. antioch/session/views/articulation.py +245 -0
  21. antioch/session/views/basis_curve.py +186 -0
  22. antioch/session/views/camera.py +92 -0
  23. antioch/session/views/collision.py +75 -0
  24. antioch/session/views/geometry.py +74 -0
  25. antioch/session/views/ground_plane.py +63 -0
  26. antioch/session/views/imu.py +73 -0
  27. antioch/session/views/joint.py +64 -0
  28. antioch/session/views/light.py +175 -0
  29. antioch/session/views/pir_sensor.py +140 -0
  30. antioch/session/views/radar.py +73 -0
  31. antioch/session/views/rigid_body.py +282 -0
  32. antioch/session/views/xform.py +119 -0
  33. antioch_py-2.0.6.dist-info/METADATA +115 -0
  34. antioch_py-2.0.6.dist-info/RECORD +99 -0
  35. antioch_py-2.0.6.dist-info/WHEEL +5 -0
  36. antioch_py-2.0.6.dist-info/entry_points.txt +2 -0
  37. antioch_py-2.0.6.dist-info/top_level.txt +2 -0
  38. common/__init__.py +0 -0
  39. common/ark/__init__.py +60 -0
  40. common/ark/ark.py +128 -0
  41. common/ark/hardware.py +121 -0
  42. common/ark/kinematics.py +31 -0
  43. common/ark/module.py +85 -0
  44. common/ark/node.py +94 -0
  45. common/ark/scheduler.py +439 -0
  46. common/ark/sim.py +33 -0
  47. common/assets/__init__.py +3 -0
  48. common/constants.py +47 -0
  49. common/core/__init__.py +52 -0
  50. common/core/agent.py +296 -0
  51. common/core/auth.py +305 -0
  52. common/core/registry.py +331 -0
  53. common/core/task.py +36 -0
  54. common/message/__init__.py +59 -0
  55. common/message/annotation.py +89 -0
  56. common/message/array.py +500 -0
  57. common/message/base.py +517 -0
  58. common/message/camera.py +91 -0
  59. common/message/color.py +139 -0
  60. common/message/frame.py +50 -0
  61. common/message/image.py +171 -0
  62. common/message/imu.py +14 -0
  63. common/message/joint.py +47 -0
  64. common/message/log.py +31 -0
  65. common/message/pir.py +16 -0
  66. common/message/point.py +109 -0
  67. common/message/point_cloud.py +63 -0
  68. common/message/pose.py +148 -0
  69. common/message/quaternion.py +273 -0
  70. common/message/radar.py +58 -0
  71. common/message/types.py +37 -0
  72. common/message/vector.py +786 -0
  73. common/rome/__init__.py +9 -0
  74. common/rome/client.py +430 -0
  75. common/rome/error.py +16 -0
  76. common/session/__init__.py +54 -0
  77. common/session/environment.py +31 -0
  78. common/session/sim.py +240 -0
  79. common/session/views/__init__.py +263 -0
  80. common/session/views/animation.py +73 -0
  81. common/session/views/articulation.py +184 -0
  82. common/session/views/basis_curve.py +102 -0
  83. common/session/views/camera.py +147 -0
  84. common/session/views/collision.py +59 -0
  85. common/session/views/geometry.py +102 -0
  86. common/session/views/ground_plane.py +41 -0
  87. common/session/views/imu.py +66 -0
  88. common/session/views/joint.py +81 -0
  89. common/session/views/light.py +96 -0
  90. common/session/views/pir_sensor.py +115 -0
  91. common/session/views/radar.py +82 -0
  92. common/session/views/rigid_body.py +236 -0
  93. common/session/views/viewport.py +21 -0
  94. common/session/views/xform.py +39 -0
  95. common/utils/__init__.py +4 -0
  96. common/utils/comms.py +571 -0
  97. common/utils/logger.py +123 -0
  98. common/utils/time.py +42 -0
  99. common/utils/usd.py +12 -0
@@ -0,0 +1,218 @@
1
+ import json
2
+ import os
3
+ import signal
4
+ import threading
5
+ from collections.abc import Callable
6
+
7
+ from antioch.module.execution import Execution
8
+ from antioch.module.node import Node
9
+ from common.ark.ark import Ark, Environment
10
+ from common.ark.module import ModuleParameter
11
+ from common.message import Message
12
+ from common.utils.comms import CommsSession
13
+ from common.utils.logger import Logger
14
+ from common.utils.time import now_us
15
+
16
+ AGENT_MODULE_READY_PATH = "_agent/module_ready"
17
+ AGENT_MODULE_START_PATH = "_agent/module_start"
18
+
19
+
20
+ class ModuleReady(Message):
21
+ """
22
+ Module ready message sent during handshake.
23
+ """
24
+
25
+ _type = "antioch/agent/module_ready"
26
+ module_name: str
27
+
28
+
29
+ class ModuleStart(Message):
30
+ """
31
+ Module start message received during handshake.
32
+ """
33
+
34
+ _type = "antioch/agent/module_start"
35
+ global_start_time_us: int
36
+
37
+
38
+ class Module:
39
+ """
40
+ Core module class for implementing Antioch modules in Python, which supports two
41
+ mode of operation:
42
+
43
+ > Container Mode (default): The module runs inside a Kubernetes pod managed by the
44
+ Antioch agent. Configuration is automatically loaded from environment variables
45
+ set by the agent (_MODULE_NAME, _ARK, _ENVIRONMENT, _DEBUG).
46
+
47
+ > Local Mode: The module runs standalone for testing and development. Configuration
48
+ must be explicitly provided via constructor parameters (module_name, ark, environment,
49
+ debug).
50
+
51
+ After creating a Module instance, you can access module metadata like module.ark,
52
+ module.parameters, module.environment, and module.debug to initialize resources
53
+ before registering node callbacks.
54
+
55
+ Users register node callbacks via register() and then call spin() to start execution.
56
+ The Ark will not start until spin() is called, which begins the handshake with the
57
+ agent and starts processing node callbacks.
58
+
59
+ Example:
60
+ # Container mode
61
+ module = Module()
62
+ module.register("node1", callback1)
63
+ module.register("node2", callback2)
64
+ module.spin()
65
+
66
+ # Local mode (for testing)
67
+ module = Module(
68
+ module_name="my_module",
69
+ environment=Environment.SIM,
70
+ ark=my_ark,
71
+ debug=True,
72
+ )
73
+ module.register("node1", callback1)
74
+ module.spin()
75
+ """
76
+
77
+ module_name: str
78
+ ark: Ark
79
+ environment: Environment
80
+ debug: bool
81
+ parameters: dict[str, "ModuleParameter"]
82
+
83
+ def __init__(
84
+ self,
85
+ module_name: str | None = None,
86
+ ark: Ark | None = None,
87
+ environment: Environment = Environment.REAL,
88
+ debug: bool = False,
89
+ ):
90
+ """
91
+ Initialize module.
92
+
93
+ :param module_name: Module name (for local mode).
94
+ :param ark: Ark definition (for local mode).
95
+ :param environment: Execution environment (for local mode).
96
+ :param debug: Enable debug mode for loggers (for local mode).
97
+ """
98
+
99
+ # Parse module configuration
100
+ self._is_local_mode = module_name is not None
101
+ if not self._is_local_mode:
102
+ module_name = os.environ.get("_MODULE_NAME")
103
+ ark = Ark.model_validate(json.loads(os.environ["_ARK"])) if "_ARK" in os.environ else None
104
+ environment = Environment(os.environ.get("_ENVIRONMENT", "real"))
105
+ debug = os.environ.get("_DEBUG", "false").lower() == "true"
106
+
107
+ # Validate required configuration
108
+ if module_name is None:
109
+ raise ValueError("Module is missing module name")
110
+ if ark is None:
111
+ raise ValueError("Module is missing ark configuration")
112
+
113
+ # Extract module config from Ark
114
+ module_config = next((m for m in ark.modules if m.name == module_name), None)
115
+ if module_config is None:
116
+ raise ValueError(f"Module '{module_name}' not found in Ark")
117
+
118
+ self.module_name = module_name
119
+ self.ark = ark
120
+ self.environment = environment
121
+ self.debug = debug
122
+ self.parameters = module_config.parameters
123
+
124
+ self._module_config = module_config
125
+ self._shutdown_event = threading.Event()
126
+ self._node_callbacks: dict[str, Callable[[Execution], None]] = {}
127
+ self._nodes: dict[str, Node] = {}
128
+
129
+ def register(self, name: str, callback: Callable[[Execution], None]) -> None:
130
+ """
131
+ Register a node callback.
132
+
133
+ :param name: Node name.
134
+ :param callback: User callback function invoked on each node execution.
135
+ """
136
+
137
+ self._node_callbacks[name] = callback
138
+
139
+ def spin(self) -> None:
140
+ """
141
+ Initialize module and wait for shutdown signal.
142
+ """
143
+
144
+ comms = CommsSession()
145
+ logger = Logger(comms, self.module_name, debug=self.debug, print_logs=True)
146
+ global_start_time_us = now_us() if self._is_local_mode else self._perform_agent_handshake(comms)
147
+
148
+ # Validate node callbacks
149
+ for node_name in self._node_callbacks:
150
+ if node_name not in self._module_config.nodes:
151
+ raise ValueError(f"Node '{node_name}' not in module config")
152
+ for node_name in self._module_config.nodes:
153
+ if node_name not in self._node_callbacks:
154
+ raise ValueError(f"Missing callback for node '{node_name}'")
155
+
156
+ # Start node threads
157
+ for name, callback in self._node_callbacks.items():
158
+ self._nodes[name] = Node(
159
+ module_name=self.module_name,
160
+ node_name=name,
161
+ module_config=self._module_config,
162
+ node_config=self._module_config.nodes[name],
163
+ ark_edges=self.ark.edges,
164
+ ark_modules=self.ark.modules,
165
+ global_start_time_us=global_start_time_us,
166
+ environment=self.environment,
167
+ debug=self.debug,
168
+ callback=callback,
169
+ )
170
+
171
+ # Wait for shutdown signal
172
+ signal.signal(signal.SIGTERM, self._handle_shutdown)
173
+ signal.signal(signal.SIGINT, self._handle_shutdown)
174
+ self._shutdown_event.wait()
175
+
176
+ logger.info("Module exiting")
177
+ comms.close()
178
+
179
+ def join(self, timeout: float | None = None) -> None:
180
+ """
181
+ Wait for all node threads to finish.
182
+
183
+ Blocks until all node threads have completed execution or until
184
+ the timeout is reached. Useful for ensuring all nodes have finished
185
+ processing before the module exits.
186
+
187
+ :param timeout: Maximum time to wait per node in seconds (None for infinite).
188
+ """
189
+
190
+ for node in self._nodes.values():
191
+ node.join(timeout=timeout)
192
+
193
+ def _perform_agent_handshake(self, comms: CommsSession) -> int:
194
+ """
195
+ Perform handshake with agent to receive global start time.
196
+
197
+ Publishes ready message and blocks waiting for start message containing
198
+ the global start time for synchronization.
199
+
200
+ :param comms: Comms session to use for communication.
201
+ :return: Global start time in microseconds.
202
+ """
203
+
204
+ ready_publisher = comms.declare_publisher(AGENT_MODULE_READY_PATH)
205
+ start_subscriber = comms.declare_subscriber(AGENT_MODULE_START_PATH)
206
+ ready_publisher.publish(ModuleReady(module_name=self.module_name))
207
+ start_msg = start_subscriber.recv(ModuleStart)
208
+ return start_msg.global_start_time_us
209
+
210
+ def _handle_shutdown(self, _signum, _frame) -> None:
211
+ """
212
+ Handle shutdown signal by stopping all node threads and
213
+ exiting the module.
214
+ """
215
+
216
+ for node in self._nodes.values():
217
+ node.stop()
218
+ self._shutdown_event.set()
antioch/module/node.py ADDED
@@ -0,0 +1,357 @@
1
+ import traceback
2
+ from collections.abc import Callable
3
+ from threading import Event, Thread
4
+
5
+ from antioch.module.clock import Clock
6
+ from antioch.module.execution import Execution
7
+ from antioch.module.input import NodeInputBuffer
8
+ from antioch.module.token import Token, TokenType
9
+ from common.ark.ark import Environment
10
+ from common.ark.module import Module
11
+ from common.ark.node import Node as NodeConfig
12
+ from common.ark.scheduler import InputToken, NodeEdge, NodeStartEvent, OnlineScheduler
13
+ from common.ark.sim import SimNodeComplete, SimNodeStart
14
+ from common.utils.comms import CommsPublisher, CommsSession
15
+ from common.utils.logger import Logger
16
+ from common.utils.time import now_us
17
+
18
+ TOKEN_OUTPUT_PATH = "_token/{path}"
19
+ NODE_START_SUBSCRIBER_PATH = "_ark/node_start/{module}/{node}"
20
+ NODE_COMPLETE_PUBLISHER_PATH = "_ark/node_complete/{module}/{node}"
21
+
22
+
23
+ class Node:
24
+ """
25
+ Independent node execution thread.
26
+
27
+ Each node runs in its own thread, walks the deterministic schedule independently,
28
+ and executes user callbacks at the appropriate logical times.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ module_name: str,
34
+ node_name: str,
35
+ module_config: Module,
36
+ node_config: NodeConfig,
37
+ ark_edges: list[NodeEdge],
38
+ ark_modules: list[Module],
39
+ global_start_time_us: int,
40
+ environment: Environment,
41
+ debug: bool,
42
+ callback: Callable[[Execution], None],
43
+ ):
44
+ """
45
+ Initialize node and start thread immediately.
46
+
47
+ :param module_name: Module containing this node.
48
+ :param node_name: Name of this node.
49
+ :param module_config: Module configuration from Ark.
50
+ :param node_config: Node configuration.
51
+ :param ark_edges: All edges in the Ark.
52
+ :param ark_modules: All modules in the Ark.
53
+ :param global_start_time_us: Global start time (only used in real mode).
54
+ :param environment: Execution environment (SIM or REAL).
55
+ :param debug: Enable debug mode for loggers.
56
+ :param callback: User callback function to execute.
57
+ """
58
+
59
+ self._module_name = module_name
60
+ self._node_name = node_name
61
+ self._module_config = module_config
62
+ self._node_config = node_config
63
+ self._ark_edges = ark_edges
64
+ self._ark_modules = ark_modules
65
+ self._global_start_time_us = global_start_time_us
66
+ self._environment = environment
67
+ self._callback = callback
68
+
69
+ self._comms = CommsSession()
70
+ self._logger = Logger(self._comms, module_name, debug=debug, print_logs=True)
71
+ self._execution_logger = Logger(self._comms, module_name, debug=debug, print_logs=True)
72
+ self._shutdown_requested = Event()
73
+
74
+ # Create input buffers for all inputs
75
+ self._input_buffers: dict[str, NodeInputBuffer] = {}
76
+ for input_name, input_config in node_config.inputs.items():
77
+ self._input_buffers[input_name] = NodeInputBuffer(input_name, input_config, self._comms)
78
+
79
+ # Create output publishers for all outputs
80
+ self._output_publishers: dict[str, CommsPublisher] = {}
81
+ for output_name, output_config in node_config.outputs.items():
82
+ path = TOKEN_OUTPUT_PATH.format(path=output_config.path)
83
+ self._output_publishers[output_name] = self._comms.declare_publisher(path)
84
+
85
+ # Create reusable execution context
86
+ self._execution = Execution(
87
+ name=node_config.name,
88
+ budget_us=node_config.budget_us,
89
+ parameters=module_config.parameters,
90
+ outputs=node_config.outputs,
91
+ logger=self._execution_logger,
92
+ )
93
+
94
+ # Start thread immediately
95
+ thread_target = self._run_sim if self._environment == Environment.SIM else self._run_real
96
+ self._thread = Thread(target=thread_target, daemon=True)
97
+ self._thread.start()
98
+
99
+ def stop(self) -> None:
100
+ """
101
+ Stop the node and clean up resources.
102
+
103
+ Sets the shutdown event, causing the node's execution loop to exit,
104
+ and cleans up all communication resources.
105
+ """
106
+
107
+ self._logger.info("Stopping node")
108
+ self._shutdown_requested.set()
109
+
110
+ # Close all input buffers
111
+ for input_buffer in self._input_buffers.values():
112
+ input_buffer.close()
113
+
114
+ # Close all output publishers
115
+ for publisher in self._output_publishers.values():
116
+ publisher.close()
117
+
118
+ # Close comms session
119
+ self._comms.close()
120
+
121
+ def join(self, timeout: float | None = None) -> None:
122
+ """
123
+ Wait for node thread to finish.
124
+
125
+ :param timeout: Maximum time to wait in seconds (None for infinite).
126
+ """
127
+
128
+ self._thread.join(timeout=timeout)
129
+
130
+ def _run_sim(self) -> None:
131
+ """
132
+ Sim mode execution loop.
133
+
134
+ Waits for SimNodeStart messages via async subscriber with timeout checking
135
+ for shutdown events.
136
+ """
137
+
138
+ start_path = NODE_START_SUBSCRIBER_PATH.format(module=self._module_name, node=self._node_name)
139
+ complete_path = NODE_COMPLETE_PUBLISHER_PATH.format(module=self._module_name, node=self._node_name)
140
+ start_subscriber = self._comms.declare_async_subscriber(start_path)
141
+ complete_publisher = self._comms.declare_publisher(complete_path)
142
+
143
+ while not self._shutdown_requested.is_set():
144
+ # Wait for node start event with shutdown check
145
+ start = start_subscriber.recv_timeout(SimNodeStart, timeout=0.1)
146
+ if start is None:
147
+ continue
148
+
149
+ # Update logger times
150
+ self._logger.set_let(start.start_let_us)
151
+ self._execution.logger.set_let(start.start_let_us)
152
+
153
+ # Gather and filter input tokens
154
+ input_data = self._gather_inputs(start.start_let_us, start.input_tokens)
155
+ if input_data is None:
156
+ self._logger.warning(f"Skipping node {self._node_name} at {start.start_let_us} because of missing inputs")
157
+ self._publish_skipped_tokens(start.start_let_us, start.start_timestamp_us)
158
+ continue
159
+
160
+ # Start execution with runtime data
161
+ self._execution.start(
162
+ let_us=start.start_let_us,
163
+ input_data=input_data,
164
+ hardware_reads=start.hardware_reads,
165
+ )
166
+
167
+ # Execute user callback
168
+ try:
169
+ self._callback(self._execution)
170
+ except Exception as e:
171
+ self._publish_error_tokens(start.start_let_us, str(e), self._execution._start_timestamp_us)
172
+ self._logger.error(f"Execution failed: {e}\n{traceback.format_exc()}")
173
+ continue
174
+
175
+ # Check for budget overrun
176
+ if self._execution.elapsed_us > self._node_config.budget_us:
177
+ self._logger.error(f"Overrun: {self._execution.elapsed_us}us > {self._node_config.budget_us}us at {start.start_let_us}us")
178
+ continue
179
+
180
+ # Publish output tokens
181
+ for output_name, publisher in self._output_publishers.items():
182
+ payload = self._execution._output_data.get(output_name)
183
+ publisher.publish(
184
+ Token(
185
+ module_name=self._module_name,
186
+ node_name=self._node_name,
187
+ output_name=output_name,
188
+ let_us=start.start_let_us,
189
+ start_timestamp_us=self._execution._start_timestamp_us,
190
+ budget_us=self._node_config.budget_us,
191
+ status=TokenType.DATA if payload is not None else TokenType.SHORT_CIRCUIT,
192
+ payload=payload,
193
+ )
194
+ )
195
+
196
+ # Publish completion (only if no overrun or error)
197
+ complete_publisher.publish(
198
+ SimNodeComplete(
199
+ module_name=self._module_name,
200
+ node_name=self._node_name,
201
+ completion_let_us=start.start_let_us + self._node_config.budget_us,
202
+ hardware_writes=self._execution._hardware_writes if self._execution._hardware_writes else None,
203
+ )
204
+ )
205
+
206
+ def _run_real(self) -> None:
207
+ """
208
+ Real mode execution loop.
209
+
210
+ Sleeps until event times using clock-based synchronization.
211
+ Creates scheduler locally since it's only needed in real mode.
212
+ """
213
+
214
+ scheduler = OnlineScheduler(self._ark_edges, self._ark_modules)
215
+ clock = Clock(self._global_start_time_us, event=self._shutdown_requested)
216
+
217
+ while not self._shutdown_requested.is_set():
218
+ # Skip to next event for this node
219
+ event = scheduler.next()
220
+ if (
221
+ not isinstance(event, NodeStartEvent)
222
+ or (event.module, event.node) != (self._module_name, self._node_name)
223
+ or clock.let_us > event.start_let_us
224
+ ):
225
+ continue
226
+
227
+ # Sleep until event time with shutdown check
228
+ if not clock.wait_until(event.start_let_us):
229
+ break
230
+
231
+ # Update logger times
232
+ self._logger.set_let(event.start_let_us)
233
+ self._execution_logger.set_let(event.start_let_us)
234
+
235
+ # Gather and filter input tokens
236
+ input_data = self._gather_inputs(event.start_let_us, event.input_tokens)
237
+ if input_data is None:
238
+ self._logger.warning(f"Skipping node {self._node_name} at {event.start_let_us} because of missing inputs")
239
+ self._publish_skipped_tokens(event.start_let_us, now_us())
240
+ continue
241
+
242
+ # Start execution with runtime data (no hardware reads in real mode)
243
+ self._execution.start(
244
+ let_us=event.start_let_us,
245
+ input_data=input_data,
246
+ hardware_reads={},
247
+ )
248
+
249
+ # Execute user callback
250
+ try:
251
+ self._callback(self._execution)
252
+ except Exception as e:
253
+ self._publish_error_tokens(event.start_let_us, str(e), self._execution._start_timestamp_us)
254
+ self._logger.error(f"Execution failed: {e}\n{traceback.format_exc()}")
255
+ continue
256
+
257
+ # Check for budget overrun
258
+ if self._execution.elapsed_us > self._node_config.budget_us:
259
+ self._logger.error(f"Overrun: {self._execution.elapsed_us}us > {self._node_config.budget_us}us at {event.start_let_us}us")
260
+ continue
261
+
262
+ # Publish output tokens
263
+ for output_name, publisher in self._output_publishers.items():
264
+ payload = self._execution._output_data.get(output_name)
265
+ publisher.publish(
266
+ Token(
267
+ module_name=self._module_name,
268
+ node_name=self._node_name,
269
+ output_name=output_name,
270
+ let_us=event.start_let_us,
271
+ start_timestamp_us=self._execution._start_timestamp_us,
272
+ budget_us=self._node_config.budget_us,
273
+ status=TokenType.DATA if payload is not None else TokenType.SHORT_CIRCUIT,
274
+ payload=payload,
275
+ )
276
+ )
277
+
278
+ def _gather_inputs(self, let_us: int, expected_tokens: list[InputToken]) -> dict[str, list[Token]] | None:
279
+ """
280
+ Gather input tokens and apply buffer policies.
281
+
282
+ Detects missing tokens (overruns) and adds them to buffers before execution.
283
+
284
+ :param let_us: Current logical execution time.
285
+ :param expected_tokens: List of InputToken objects expected for this execution.
286
+ :return: Dictionary of filtered tokens by input name, or None if required input missing.
287
+ """
288
+
289
+ # Add missing tokens as overruns to buffers
290
+ for tok in expected_tokens:
291
+ input_buffer = self._input_buffers[tok.target_input_name]
292
+ if input_buffer.get_token(tok.let_us, tok.source_module, tok.source_node) is None:
293
+ input_buffer.add_token(
294
+ Token(
295
+ module_name=tok.source_module,
296
+ node_name=tok.source_node,
297
+ output_name=tok.source_output_name,
298
+ let_us=tok.let_us,
299
+ budget_us=tok.budget_us,
300
+ start_timestamp_us=tok.let_us,
301
+ status=TokenType.OVERRUN,
302
+ )
303
+ )
304
+
305
+ # Prepare execution by applying buffer policies
306
+ input_data = {}
307
+ for input_name, input_buffer in self._input_buffers.items():
308
+ filtered_tokens = input_buffer.prepare_execution(let_us)
309
+ if filtered_tokens is None:
310
+ return None
311
+ input_data[input_name] = filtered_tokens
312
+
313
+ return input_data
314
+
315
+ def _publish_skipped_tokens(self, let_us: int, start_timestamp_us: int) -> None:
316
+ """
317
+ Publish skipped tokens for all outputs.
318
+
319
+ :param let_us: Logical execution time.
320
+ :param start_timestamp_us: Start timestamp for token.
321
+ """
322
+
323
+ for output_name, publisher in self._output_publishers.items():
324
+ publisher.publish(
325
+ Token(
326
+ module_name=self._module_name,
327
+ node_name=self._node_name,
328
+ output_name=output_name,
329
+ let_us=let_us,
330
+ start_timestamp_us=start_timestamp_us,
331
+ budget_us=self._node_config.budget_us,
332
+ status=TokenType.SKIPPED,
333
+ )
334
+ )
335
+
336
+ def _publish_error_tokens(self, let_us: int, error: str, start_timestamp_us: int) -> None:
337
+ """
338
+ Publish error tokens for all outputs.
339
+
340
+ :param let_us: Logical execution time.
341
+ :param error: Error message.
342
+ :param start_timestamp_us: Start timestamp for token.
343
+ """
344
+
345
+ for output_name, publisher in self._output_publishers.items():
346
+ publisher.publish(
347
+ Token(
348
+ module_name=self._module_name,
349
+ node_name=self._node_name,
350
+ output_name=output_name,
351
+ let_us=let_us,
352
+ start_timestamp_us=start_timestamp_us,
353
+ budget_us=self._node_config.budget_us,
354
+ status=TokenType.ERROR,
355
+ error=error,
356
+ )
357
+ )
@@ -0,0 +1,42 @@
1
+ from enum import Enum
2
+
3
+ from common.message import Message
4
+ from common.utils.time import now_us
5
+
6
+
7
+ class TokenType(str, Enum):
8
+ """
9
+ Token status types.
10
+ """
11
+
12
+ DATA = "data"
13
+ SHORT_CIRCUIT = "short_circuit"
14
+ ERROR = "error"
15
+ OVERRUN = "overrun"
16
+ SKIPPED = "skipped"
17
+
18
+
19
+ class Token(Message):
20
+ """
21
+ Token representing data flow between nodes.
22
+ """
23
+
24
+ _type = "antioch/token"
25
+ module_name: str
26
+ node_name: str
27
+ output_name: str
28
+ let_us: int
29
+ budget_us: int
30
+ start_timestamp_us: int
31
+ status: TokenType
32
+ payload: bytes | None = None
33
+ error: str | None = None
34
+
35
+ def elapsed(self) -> int:
36
+ """
37
+ Get elapsed time in microseconds since token creation.
38
+
39
+ :return: Elapsed time in microseconds.
40
+ """
41
+
42
+ return now_us() - self.start_timestamp_us