fastpilot 0.1.0__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.
Files changed (70) hide show
  1. fastpilot/__init__.py +171 -0
  2. fastpilot/contrib/__init__.py +30 -0
  3. fastpilot/contrib/echo.py +61 -0
  4. fastpilot/contrib/robot.py +168 -0
  5. fastpilot/core/__init__.py +7 -0
  6. fastpilot/core/children.py +105 -0
  7. fastpilot/core/factory.py +34 -0
  8. fastpilot/core/handle.py +59 -0
  9. fastpilot/core/logical_time.py +142 -0
  10. fastpilot/core/queue.py +184 -0
  11. fastpilot/core/realtime.py +61 -0
  12. fastpilot/core/runtime.py +781 -0
  13. fastpilot/core/station.py +141 -0
  14. fastpilot/driver.py +175 -0
  15. fastpilot/ipython/__init__.py +9 -0
  16. fastpilot/ipython/display.py +54 -0
  17. fastpilot/observation/__init__.py +38 -0
  18. fastpilot/observation/bus.py +185 -0
  19. fastpilot/observation/runtime_history.py +98 -0
  20. fastpilot/observation/store.py +245 -0
  21. fastpilot/observation/updates.py +111 -0
  22. fastpilot/protocol/__init__.py +147 -0
  23. fastpilot/protocol/catalog.py +465 -0
  24. fastpilot/protocol/commands.py +154 -0
  25. fastpilot/protocol/contracts.py +69 -0
  26. fastpilot/protocol/declarations.py +161 -0
  27. fastpilot/protocol/drafts.py +70 -0
  28. fastpilot/protocol/effects.py +218 -0
  29. fastpilot/protocol/errors.py +13 -0
  30. fastpilot/protocol/events.py +117 -0
  31. fastpilot/protocol/hosting.py +20 -0
  32. fastpilot/protocol/queue.py +77 -0
  33. fastpilot/protocol/receipts.py +50 -0
  34. fastpilot/protocol/schema.py +144 -0
  35. fastpilot/protocol/system_facts.py +228 -0
  36. fastpilot/protocol/triggers.py +227 -0
  37. fastpilot/protocol/values.py +111 -0
  38. fastpilot/remote/__init__.py +7 -0
  39. fastpilot/remote/connect.py +86 -0
  40. fastpilot/remote/errors.py +21 -0
  41. fastpilot/remote/handle.py +47 -0
  42. fastpilot/remote/runtime.py +530 -0
  43. fastpilot/service/__init__.py +23 -0
  44. fastpilot/service/__main__.py +32 -0
  45. fastpilot/service/app.py +486 -0
  46. fastpilot/service/browser.py +191 -0
  47. fastpilot/service/hosting.py +232 -0
  48. fastpilot/service/ledger.py +43 -0
  49. fastpilot/service/mount.py +53 -0
  50. fastpilot/service/streaming.py +53 -0
  51. fastpilot/state/__init__.py +32 -0
  52. fastpilot/state/chart.py +46 -0
  53. fastpilot/state/handlers.py +136 -0
  54. fastpilot/state/machine.py +169 -0
  55. fastpilot/state/participant.py +227 -0
  56. fastpilot/time/__init__.py +27 -0
  57. fastpilot/time/calendar.py +48 -0
  58. fastpilot/time/clock.py +31 -0
  59. fastpilot/time/rules.py +63 -0
  60. fastpilot/time/validation.py +25 -0
  61. fastpilot/wire/__init__.py +39 -0
  62. fastpilot/wire/commands.py +62 -0
  63. fastpilot/wire/observations.py +345 -0
  64. fastpilot/wire/protocol_values.py +307 -0
  65. fastpilot/wire/values.py +102 -0
  66. fastpilot/wire/version.py +5 -0
  67. fastpilot-0.1.0.dist-info/METADATA +384 -0
  68. fastpilot-0.1.0.dist-info/RECORD +70 -0
  69. fastpilot-0.1.0.dist-info/WHEEL +4 -0
  70. fastpilot-0.1.0.dist-info/licenses/LICENSE +203 -0
fastpilot/__init__.py ADDED
@@ -0,0 +1,171 @@
1
+ """FastPilot:Python 业务对象的控制、观察与远程访问协议。"""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from fastpilot.core import Runtime, RuntimeHandle, make_runtime
6
+ from fastpilot.driver import CommandSurface, Driver
7
+ from fastpilot.observation import (
8
+ HistoryStore,
9
+ HistoryStoreFactory,
10
+ MemoryHistoryStore,
11
+ Predicate,
12
+ PredicateLike,
13
+ RuntimeHistoryStores,
14
+ SQLiteSpoolHistoryStore,
15
+ Subscription,
16
+ temporary_sqlite_history,
17
+ )
18
+ from fastpilot.protocol import (
19
+ Advance,
20
+ AllOf,
21
+ AnyOf,
22
+ AtTime,
23
+ CancelEntry,
24
+ CapabilityError,
25
+ Command,
26
+ CommandEffect,
27
+ CommandOutcome,
28
+ CommandQueued,
29
+ CommandReceipt,
30
+ Commit,
31
+ DomainCommandHandled,
32
+ DraftConflictError,
33
+ Enqueue,
34
+ Event,
35
+ Fact,
36
+ FastPilotError,
37
+ HostedRuntimeView,
38
+ Immediately,
39
+ MoveEntry,
40
+ OnFact,
41
+ OnStateEntered,
42
+ Pause,
43
+ Paused,
44
+ QueueEntryStatus,
45
+ QueueEntryView,
46
+ QueueSnapshot,
47
+ Reject,
48
+ Resume,
49
+ Resumed,
50
+ RuntimeUpdate,
51
+ SetSpeed,
52
+ Snapshot,
53
+ Start,
54
+ Started,
55
+ StateEntered,
56
+ Stop,
57
+ Stopped,
58
+ TransitionDraft,
59
+ TransitionView,
60
+ Trigger,
61
+ WhenState,
62
+ command,
63
+ fact,
64
+ framework_protocol,
65
+ )
66
+ from fastpilot.state import (
67
+ DataParticipant,
68
+ ParticipantContext,
69
+ RuntimeParticipant,
70
+ StateChart,
71
+ StatefulParticipant,
72
+ TickParticipant,
73
+ emits,
74
+ handles,
75
+ participant_protocol,
76
+ )
77
+
78
+ if TYPE_CHECKING:
79
+ from fastpilot.remote import RemoteRuntime
80
+
81
+ __version__ = "0.1.0"
82
+
83
+
84
+ def connect(
85
+ base_url: str,
86
+ *,
87
+ connect_timeout: float = 3.0,
88
+ command_timeout: float = 30.0,
89
+ ) -> "RemoteRuntime":
90
+ """连接独立 FastPilot 服务,并延迟加载远程可选依赖。"""
91
+ from fastpilot.remote import connect as remote_connect
92
+
93
+ return remote_connect(
94
+ base_url,
95
+ connect_timeout=connect_timeout,
96
+ command_timeout=command_timeout,
97
+ )
98
+
99
+
100
+ __all__ = [
101
+ "Advance",
102
+ "AllOf",
103
+ "AnyOf",
104
+ "AtTime",
105
+ "CancelEntry",
106
+ "CapabilityError",
107
+ "Command",
108
+ "CommandQueued",
109
+ "CommandOutcome",
110
+ "CommandEffect",
111
+ "CommandReceipt",
112
+ "CommandSurface",
113
+ "Commit",
114
+ "DomainCommandHandled",
115
+ "DataParticipant",
116
+ "DraftConflictError",
117
+ "Driver",
118
+ "Enqueue",
119
+ "Event",
120
+ "Fact",
121
+ "FastPilotError",
122
+ "HistoryStore",
123
+ "HistoryStoreFactory",
124
+ "HostedRuntimeView",
125
+ "Immediately",
126
+ "MemoryHistoryStore",
127
+ "MoveEntry",
128
+ "OnFact",
129
+ "OnStateEntered",
130
+ "ParticipantContext",
131
+ "Pause",
132
+ "Paused",
133
+ "Predicate",
134
+ "PredicateLike",
135
+ "QueueEntryStatus",
136
+ "QueueEntryView",
137
+ "QueueSnapshot",
138
+ "Reject",
139
+ "Resume",
140
+ "Resumed",
141
+ "Runtime",
142
+ "RuntimeHandle",
143
+ "RuntimeHistoryStores",
144
+ "RuntimeParticipant",
145
+ "RuntimeUpdate",
146
+ "SetSpeed",
147
+ "Snapshot",
148
+ "SQLiteSpoolHistoryStore",
149
+ "Start",
150
+ "Started",
151
+ "StateEntered",
152
+ "StatefulParticipant",
153
+ "StateChart",
154
+ "Stop",
155
+ "Stopped",
156
+ "Subscription",
157
+ "TickParticipant",
158
+ "TransitionDraft",
159
+ "TransitionView",
160
+ "Trigger",
161
+ "WhenState",
162
+ "command",
163
+ "emits",
164
+ "fact",
165
+ "framework_protocol",
166
+ "handles",
167
+ "participant_protocol",
168
+ "temporary_sqlite_history",
169
+ "connect",
170
+ "make_runtime",
171
+ ]
@@ -0,0 +1,30 @@
1
+ """用于验证 FastPilot 公共协议的具体参与者。"""
2
+
3
+ from fastpilot.contrib.echo import Echo, Echoed, EchoMessage, make_echo
4
+ from fastpilot.contrib.robot import (
5
+ ROBOT_STATECHART,
6
+ ContinueWalking,
7
+ Obstacle,
8
+ Rest,
9
+ RobotParticipant,
10
+ SetEnergy,
11
+ Wake,
12
+ Walk,
13
+ make_robot,
14
+ )
15
+
16
+ __all__ = [
17
+ "Echo",
18
+ "EchoMessage",
19
+ "Echoed",
20
+ "ROBOT_STATECHART",
21
+ "ContinueWalking",
22
+ "Obstacle",
23
+ "Rest",
24
+ "RobotParticipant",
25
+ "SetEnergy",
26
+ "Wake",
27
+ "Walk",
28
+ "make_echo",
29
+ "make_robot",
30
+ ]
@@ -0,0 +1,61 @@
1
+ """Echo:用最小类型化 Command/Fact 展示事件驱动业务。"""
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass, field
5
+
6
+ from fastpilot.core import Runtime, make_runtime
7
+ from fastpilot.protocol import Command, Fact, command, fact
8
+ from fastpilot.protocol.values import freeze_mapping
9
+ from fastpilot.state import ParticipantContext, emits, handles
10
+
11
+
12
+ @command("fastpilot.contrib.echo.message")
13
+ @dataclass(frozen=True, slots=True)
14
+ class EchoMessage(Command):
15
+ """一条由 Echo 记录并原样发布的领域消息。"""
16
+
17
+ name: str
18
+ payload: Mapping[str, object] = field(default_factory=dict)
19
+
20
+ def __post_init__(self) -> None:
21
+ object.__setattr__(self, "payload", freeze_mapping(self.payload))
22
+
23
+
24
+ @fact("fastpilot.contrib.echo.echoed")
25
+ @dataclass(frozen=True, slots=True)
26
+ class Echoed(Fact):
27
+ """Echo 已经接收并保存一条消息。"""
28
+
29
+ name: str
30
+ payload: Mapping[str, object] = field(default_factory=dict)
31
+
32
+ def __post_init__(self) -> None:
33
+ object.__setattr__(self, "payload", freeze_mapping(self.payload))
34
+
35
+
36
+ class Echo:
37
+ """把最近一次输入定义为 Echo 的领域状态,并发布对应领域事实。
38
+
39
+ ``last_message`` 与 ``last_payload`` 由 Echo 在回调中主动写入。这个选择
40
+ 属于 Echo 的领域模型,使 Snapshot 可以回答“最近回显了什么”。
41
+ """
42
+
43
+ @handles
44
+ @emits(Echoed)
45
+ def echo(
46
+ self,
47
+ context: ParticipantContext,
48
+ command: EchoMessage,
49
+ ) -> None:
50
+ context.write_data("last_message", command.name)
51
+ context.write_data("last_payload", dict(command.payload))
52
+ context.emit(Echoed(command.name, command.payload))
53
+
54
+
55
+ def make_echo() -> Runtime:
56
+ """创建一个可在 IPython 中直接驱动的 Echo Runtime。"""
57
+
58
+ return make_runtime(Echo())
59
+
60
+
61
+ __all__ = ["Echo", "EchoMessage", "Echoed", "make_echo"]
@@ -0,0 +1,168 @@
1
+ """散步机器人:证明状态图、逻辑时间和草稿站的厚业务参与者。"""
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass
5
+
6
+ from fastpilot.core import Runtime, make_runtime
7
+ from fastpilot.protocol import Command, TransitionDraft, command
8
+ from fastpilot.state import ParticipantContext, StateChart, handles
9
+
10
+ ROBOT_STATECHART = StateChart.create(
11
+ initial="IDLE",
12
+ transitions={
13
+ "IDLE": frozenset({"WALKING"}),
14
+ "WALKING": frozenset({"THINKING", "RESTING", "CHARGING"}),
15
+ "THINKING": frozenset({"WALKING", "RESTING"}),
16
+ "RESTING": frozenset({"WALKING"}),
17
+ "CHARGING": frozenset({"WALKING"}),
18
+ },
19
+ )
20
+
21
+
22
+ @command("fastpilot.contrib.robot.walk")
23
+ @dataclass(frozen=True, slots=True)
24
+ class Walk(Command):
25
+ """请求空闲 Robot 建立进入 WALKING 的转换草稿。"""
26
+
27
+
28
+ @command("fastpilot.contrib.robot.obstacle")
29
+ @dataclass(frozen=True, slots=True)
30
+ class Obstacle(Command):
31
+ """报告行走路径出现障碍。"""
32
+
33
+
34
+ @command("fastpilot.contrib.robot.rest")
35
+ @dataclass(frozen=True, slots=True)
36
+ class Rest(Command):
37
+ """请求思考中的 Robot 进入休息状态。"""
38
+
39
+
40
+ @command("fastpilot.contrib.robot.continue_walking")
41
+ @dataclass(frozen=True, slots=True)
42
+ class ContinueWalking(Command):
43
+ """请求思考中的 Robot 继续行走。"""
44
+
45
+
46
+ @command("fastpilot.contrib.robot.wake")
47
+ @dataclass(frozen=True, slots=True)
48
+ class Wake(Command):
49
+ """请求休息中的 Robot 重新行走。"""
50
+
51
+
52
+ @command("fastpilot.contrib.robot.set_energy")
53
+ @dataclass(frozen=True, slots=True)
54
+ class SetEnergy(Command):
55
+ """把 Robot 的领域能量设置为一个明确数值。"""
56
+
57
+ value: float
58
+
59
+
60
+ @dataclass(frozen=True, slots=True)
61
+ class RobotParticipant:
62
+ """以位置、能量和状态边展示完整 Runtime 能力。"""
63
+
64
+ @property
65
+ def statechart(self) -> StateChart:
66
+ return ROBOT_STATECHART
67
+
68
+ @property
69
+ def initial_data(self) -> Mapping[str, object]:
70
+ return {"position": 0.0, "energy": 100.0, "mood": "happy"}
71
+
72
+ def on_tick(
73
+ self,
74
+ context: ParticipantContext,
75
+ delta: float,
76
+ ) -> TransitionDraft | None:
77
+ if context.state == "WALKING":
78
+ position = _as_float(context.read_data("position", 0.0)) + delta
79
+ energy = _as_float(context.read_data("energy", 100.0)) - delta
80
+ context.write_data("position", position)
81
+ context.write_data("energy", energy)
82
+ if energy <= 20:
83
+ return context.propose("CHARGING", "energy_low", {"energy": energy})
84
+ if context.state == "RESTING" and delta > 0:
85
+ energy = min(100.0, _as_float(context.read_data("energy", 100.0)) + delta * 2)
86
+ context.write_data("energy", energy)
87
+ if context.state == "CHARGING" and delta > 0:
88
+ energy = min(100.0, _as_float(context.read_data("energy", 0.0)) + delta * 5)
89
+ context.write_data("energy", energy)
90
+ if energy >= 100:
91
+ return context.propose("WALKING", "charged")
92
+ return None
93
+
94
+ @handles
95
+ def set_energy(self, context: ParticipantContext, command: SetEnergy) -> None:
96
+ context.write_data("energy", float(command.value))
97
+
98
+ @handles
99
+ def walk(self, context: ParticipantContext, command: Walk) -> TransitionDraft | None:
100
+ del command
101
+ if context.state == "IDLE":
102
+ return context.propose("WALKING", "walk_command")
103
+ return None
104
+
105
+ @handles
106
+ def obstacle(
107
+ self,
108
+ context: ParticipantContext,
109
+ command: Obstacle,
110
+ ) -> TransitionDraft | None:
111
+ del command
112
+ if context.state == "WALKING":
113
+ return context.propose(
114
+ "THINKING",
115
+ "obstacle",
116
+ {"position": context.read_data("position", 0.0)},
117
+ )
118
+ return None
119
+
120
+ @handles
121
+ def rest(self, context: ParticipantContext, command: Rest) -> TransitionDraft | None:
122
+ del command
123
+ if context.state == "THINKING":
124
+ return context.propose("RESTING", "driver_decision")
125
+ return None
126
+
127
+ @handles
128
+ def continue_walking(
129
+ self,
130
+ context: ParticipantContext,
131
+ command: ContinueWalking,
132
+ ) -> TransitionDraft | None:
133
+ del command
134
+ if context.state == "THINKING":
135
+ return context.propose("WALKING", "driver_decision")
136
+ return None
137
+
138
+ @handles
139
+ def wake(self, context: ParticipantContext, command: Wake) -> TransitionDraft | None:
140
+ del command
141
+ if context.state == "RESTING":
142
+ return context.propose("WALKING", "wake_command")
143
+ return None
144
+
145
+
146
+ def make_robot() -> Runtime:
147
+ """创建散步机器人 Runtime。"""
148
+ return make_runtime(RobotParticipant())
149
+
150
+
151
+ def _as_float(value: object) -> float:
152
+ if not isinstance(value, (int, float)):
153
+ return 0.0
154
+ result = float(value)
155
+ return result if result == result and abs(result) != float("inf") else 0.0
156
+
157
+
158
+ __all__ = [
159
+ "ROBOT_STATECHART",
160
+ "ContinueWalking",
161
+ "Obstacle",
162
+ "Rest",
163
+ "RobotParticipant",
164
+ "SetEnergy",
165
+ "Wake",
166
+ "Walk",
167
+ "make_robot",
168
+ ]
@@ -0,0 +1,7 @@
1
+ """FastPilot 核心运行时、托管句柄与装配入口。"""
2
+
3
+ from fastpilot.core.factory import make_runtime
4
+ from fastpilot.core.handle import RuntimeHandle
5
+ from fastpilot.core.runtime import Runtime
6
+
7
+ __all__ = ["Runtime", "RuntimeHandle", "make_runtime"]
@@ -0,0 +1,105 @@
1
+ """父子 Runtime 的托管树与稳定路径解析。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import TYPE_CHECKING
7
+
8
+ from fastpilot.core.handle import RuntimeHandle
9
+ from fastpilot.protocol.contracts import Fact
10
+ from fastpilot.protocol.events import Event
11
+ from fastpilot.protocol.hosting import HostedRuntimeView
12
+ from fastpilot.protocol.system_facts import ChildAttached, ChildDetached
13
+
14
+ if TYPE_CHECKING:
15
+ from fastpilot.core.runtime import Runtime
16
+
17
+ type EmitEvent = Callable[[Fact], Event]
18
+
19
+
20
+ class ChildRegistry:
21
+ """维护一个 Runtime 直接托管的子 Runtime 与递归数字路径。"""
22
+
23
+ def __init__(self, owner: Runtime, emit: EmitEvent) -> None:
24
+ self._owner = owner
25
+ self._emit = emit
26
+ self._children: dict[int, RuntimeHandle] = {}
27
+ self._next_id = 1
28
+
29
+ def attach(self, child: Runtime, name: str | None = None) -> RuntimeHandle:
30
+ """登记子 Runtime,并验证托管树保持单父级、非自指和无环。"""
31
+ if self._owner.status == "stopped":
32
+ raise RuntimeError("已停止的 Runtime 不能再托管子 Runtime")
33
+ if child is self._owner:
34
+ raise ValueError("运行时不能托管自己")
35
+ if child._parent is not None:
36
+ raise RuntimeError("子 Runtime 已经被其他父 Runtime 托管")
37
+ if self._contains_ancestor(child):
38
+ raise ValueError("托管关系会形成循环")
39
+ child_id = self._next_id
40
+ self._next_id += 1
41
+ handle = RuntimeHandle(child_id, name or f"child-{child_id}", child)
42
+ self._children[child_id] = handle
43
+ child._parent = self._owner
44
+ self._emit(ChildAttached(child_id, handle.name))
45
+ return handle
46
+
47
+ def handles(self) -> tuple[RuntimeHandle, ...]:
48
+ """读取直接子 Runtime 的稳定句柄。"""
49
+ return tuple(self._children.values())
50
+
51
+ def views(self) -> tuple[HostedRuntimeView, ...]:
52
+ """读取可嵌入父快照的子 Runtime 投影。"""
53
+ return tuple(handle.view() for handle in self._children.values())
54
+
55
+ def resolve(self, path: str) -> Runtime:
56
+ """沿 ``1/2/...`` 数字路径解析递归子 Runtime。"""
57
+ current = self._owner
58
+ for segment in filter(None, path.split("/")):
59
+ try:
60
+ child_id = int(segment)
61
+ except ValueError as exc:
62
+ raise KeyError(f"子 Runtime 编号无效:{segment}") from exc
63
+ with current._lock:
64
+ handle = current._children.get(child_id)
65
+ current = handle._runtime
66
+ return current
67
+
68
+ def get(self, child_id: int) -> RuntimeHandle:
69
+ """按稳定编号读取一个直接子 Runtime 句柄。"""
70
+ handle = self._children.get(child_id)
71
+ if handle is None:
72
+ raise KeyError(f"子 Runtime 不存在:{child_id}")
73
+ return handle
74
+
75
+ def detach(self, child_id: int, *, stop: bool = False) -> HostedRuntimeView:
76
+ """移除子 Runtime,并返回解除关系前的投影。"""
77
+ handle = self._children.get(child_id)
78
+ if handle is None:
79
+ raise KeyError(f"不存在的子运行时:{child_id}")
80
+ child_runtime = handle.open()
81
+ if stop and child_runtime.snapshot().status != "stopped":
82
+ child_runtime.stop()
83
+ child = handle._runtime
84
+ child._parent = None
85
+ del self._children[child_id]
86
+ self._emit(ChildDetached(child_id, handle.name))
87
+ return handle.view()
88
+
89
+ def stop_all(self) -> None:
90
+ """沿每个子 Runtime 的控制面传播父级终止。"""
91
+ for handle in tuple(self._children.values()):
92
+ child = handle.open()
93
+ if child.snapshot().status != "stopped":
94
+ child.stop()
95
+
96
+ def _contains_ancestor(self, candidate: Runtime) -> bool:
97
+ cursor: Runtime | None = self._owner
98
+ while cursor is not None:
99
+ if cursor is candidate:
100
+ return True
101
+ cursor = cursor._parent
102
+ return False
103
+
104
+
105
+ __all__ = ["ChildRegistry"]
@@ -0,0 +1,34 @@
1
+ """根据参与者实际能力创建 FastPilot Runtime。"""
2
+
3
+ from collections.abc import Mapping
4
+
5
+ from fastpilot.core.runtime import Runtime
6
+ from fastpilot.observation.runtime_history import HistoryStoreFactory
7
+ from fastpilot.protocol import framework_protocol
8
+ from fastpilot.state import DataParticipant, StateMachine, participant_protocol
9
+
10
+
11
+ def make_runtime(
12
+ participant: object,
13
+ *,
14
+ history: HistoryStoreFactory | None = None,
15
+ ) -> Runtime:
16
+ """把一个领域参与者装配成可控制、可观察的 Runtime。
17
+
18
+ 工厂根据参与者提供的数据、时间与状态能力组装 Runtime。参与者表达领域
19
+ 规则,Runtime 组织命令、时间、队列、事件和草稿边界。
20
+ """
21
+ initial_data: Mapping[str, object] = (
22
+ participant.initial_data if isinstance(participant, DataParticipant) else {}
23
+ )
24
+ protocol = framework_protocol().merge(participant_protocol(participant))
25
+ history_stores = None if history is None else history(protocol)
26
+ return Runtime(
27
+ StateMachine.from_participant(participant),
28
+ dict(initial_data),
29
+ protocol=protocol,
30
+ history_stores=history_stores,
31
+ )
32
+
33
+
34
+ __all__ = ["make_runtime"]
@@ -0,0 +1,59 @@
1
+ """父子 Runtime 之间的位置句柄。
2
+
3
+ 父 Runtime 把子 Runtime 登记到托管树中,并通过句柄获得稳定编号、名称和
4
+ 观察投影。``open()`` 进入子 Runtime 自己的控制面,因此每一层都沿自己的
5
+ 时钟、锁、历史和安全边界推进。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from fastpilot.protocol.hosting import HostedRuntimeView
13
+
14
+ if TYPE_CHECKING:
15
+ from fastpilot.core.runtime import Runtime
16
+
17
+
18
+ class RuntimeHandle:
19
+ """父 Runtime 持有的子 Runtime 位置句柄。"""
20
+
21
+ __slots__ = ("_id", "_name", "_runtime")
22
+
23
+ def __init__(self, entry_id: int, name: str, runtime: Runtime) -> None:
24
+ self._id = entry_id
25
+ self._name = name
26
+ self._runtime = runtime
27
+
28
+ @property
29
+ def id(self) -> int:
30
+ """读取托管树中的稳定子 Runtime 编号。"""
31
+ return self._id
32
+
33
+ @property
34
+ def name(self) -> str:
35
+ """读取父 Runtime 用于展示和诊断的子 Runtime 名称。"""
36
+ return self._name
37
+
38
+ def open(self) -> Runtime:
39
+ """进入子 Runtime 自己的完整控制面。
40
+
41
+ 句柄表达父级托管树中的位置与名称;``open()`` 返回孩子自身拥有的
42
+ 完整 Runtime。
43
+ """
44
+
45
+ return self._runtime
46
+
47
+ def view(self) -> HostedRuntimeView:
48
+ """把子 Runtime 快照压缩成可嵌入父快照的只读投影。"""
49
+ snapshot = self._runtime.snapshot()
50
+ return HostedRuntimeView(
51
+ self._id,
52
+ self._name,
53
+ snapshot.status,
54
+ snapshot.time,
55
+ snapshot.state,
56
+ )
57
+
58
+ def __repr__(self) -> str:
59
+ return f"RuntimeHandle(id={self._id}, name={self._name!r})"