rainbow-rb-flow-manager 0.1.2.dev2__tar.gz → 0.1.2.dev4__tar.gz
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.
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/PKG-INFO +7 -7
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/pyproject.toml +7 -7
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/context.py +159 -30
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/executor.py +79 -46
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/step.py +9 -22
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow_rb_flow_manager.egg-info/PKG-INFO +7 -7
- rainbow_rb_flow_manager-0.1.2.dev4/src/rainbow_rb_flow_manager.egg-info/requires.txt +6 -0
- rainbow_rb_flow_manager-0.1.2.dev2/src/rainbow_rb_flow_manager.egg-info/requires.txt +0 -6
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/README.md +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/setup.cfg +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/.claude/skills/pyfm/SKILL.md +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/.claude/skills/pyfm/references/internals.md +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/.claude/skills/pyfm/references/testing.md +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/__init__.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/controller/base_controller.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/controller/zenoh_controller.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/examples/01_multiple_tasks.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/examples/02_single_task.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/examples/03_post_tree.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/examples/04_sub_task.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/examples/05_event_task.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/examples/README.md +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/examples/subtasks/gripper_subtask.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/exception.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/global_resolver.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/py.typed +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/schema.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow/rb_flow_manager/utils.py +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow_rb_flow_manager.egg-info/SOURCES.txt +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow_rb_flow_manager.egg-info/dependency_links.txt +0 -0
- {rainbow_rb_flow_manager-0.1.2.dev2 → rainbow_rb_flow_manager-0.1.2.dev4}/src/rainbow_rb_flow_manager.egg-info/top_level.txt +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rainbow-rb-flow-manager
|
|
3
|
-
Version: 0.1.2.
|
|
3
|
+
Version: 0.1.2.dev4
|
|
4
4
|
Summary: Rainbow Python Flow Manager
|
|
5
5
|
Author-email: Derek <dfd1123@rainbow-robotics.com>
|
|
6
6
|
Requires-Python: <3.14,>=3.11
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: rainbow-rb-utils==0.1.2.
|
|
9
|
-
Requires-Dist: rainbow-rb-zenoh==0.1.2.
|
|
10
|
-
Requires-Dist: rainbow-rb-flat-buffers==0.1.2.
|
|
11
|
-
Requires-Dist: rainbow-rb-schemas==0.1.2.
|
|
12
|
-
Requires-Dist: rainbow-rb-sdk==0.1.2.
|
|
13
|
-
Requires-Dist: rainbow-rb-log==0.1.2.
|
|
8
|
+
Requires-Dist: rainbow-rb-utils==0.1.2.dev4
|
|
9
|
+
Requires-Dist: rainbow-rb-zenoh==0.1.2.dev4
|
|
10
|
+
Requires-Dist: rainbow-rb-flat-buffers==0.1.2.dev4
|
|
11
|
+
Requires-Dist: rainbow-rb-schemas==0.1.2.dev4
|
|
12
|
+
Requires-Dist: rainbow-rb-sdk==0.1.2.dev4
|
|
13
|
+
Requires-Dist: rainbow-rb-log==0.1.2.dev4
|
|
14
14
|
|
|
15
15
|
# rb_flow_manager 사용 설명서
|
|
16
16
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "rainbow-rb-flow-manager"
|
|
7
|
-
version = "0.1.2.
|
|
7
|
+
version = "0.1.2.dev4"
|
|
8
8
|
requires-python = ">=3.11,<3.14"
|
|
9
9
|
description = "Rainbow Python Flow Manager"
|
|
10
10
|
authors = [
|
|
@@ -13,12 +13,12 @@ authors = [
|
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
|
|
15
15
|
dependencies = [
|
|
16
|
-
"rainbow-rb-utils==0.1.2.
|
|
17
|
-
"rainbow-rb-zenoh==0.1.2.
|
|
18
|
-
"rainbow-rb-flat-buffers==0.1.2.
|
|
19
|
-
"rainbow-rb-schemas==0.1.2.
|
|
20
|
-
"rainbow-rb-sdk==0.1.2.
|
|
21
|
-
"rainbow-rb-log==0.1.2.
|
|
16
|
+
"rainbow-rb-utils==0.1.2.dev4",
|
|
17
|
+
"rainbow-rb-zenoh==0.1.2.dev4",
|
|
18
|
+
"rainbow-rb-flat-buffers==0.1.2.dev4",
|
|
19
|
+
"rainbow-rb-schemas==0.1.2.dev4",
|
|
20
|
+
"rainbow-rb-sdk==0.1.2.dev4",
|
|
21
|
+
"rainbow-rb-log==0.1.2.dev4",
|
|
22
22
|
]
|
|
23
23
|
|
|
24
24
|
[tool.setuptools]
|
|
@@ -16,9 +16,8 @@ from multiprocessing.synchronize import (
|
|
|
16
16
|
)
|
|
17
17
|
from typing import Any, Literal, cast
|
|
18
18
|
|
|
19
|
-
from rainbow.rb_sdk
|
|
20
|
-
from rainbow.
|
|
21
|
-
from rainbow.rb_utils.flow_manager import eval_value, make_builtins_allow_most
|
|
19
|
+
from rainbow.rb_sdk import RBAmrSDK, RBBaseSDK, RBManipulateSDK, RBProgramSDK
|
|
20
|
+
from rainbow.rb_utils.flow_manager import collect_rb_globals, eval_value, make_builtins_allow_most
|
|
22
21
|
from rainbow.rb_utils.parser import t_to_dict
|
|
23
22
|
|
|
24
23
|
from .exception import BreakFolder, StopExecution, SubTaskHaltException
|
|
@@ -135,9 +134,17 @@ class ExecutionContext:
|
|
|
135
134
|
"""
|
|
136
135
|
code = compile(contents, "<custom_script>", "exec")
|
|
137
136
|
|
|
138
|
-
script_globals = {
|
|
137
|
+
script_globals = {
|
|
138
|
+
"__builtins__": make_builtins_allow_most(),
|
|
139
|
+
**self.program_aliases, # P_0, P_1, ... → process_id
|
|
140
|
+
**script_locals,
|
|
141
|
+
}
|
|
139
142
|
exec(code, script_globals) # pylint: disable=exec-used
|
|
140
143
|
|
|
144
|
+
@property
|
|
145
|
+
def program_aliases(self) -> dict[str, str]:
|
|
146
|
+
return dict((getattr(self, "state_dict", None) or {}).get("program_aliases") or {})
|
|
147
|
+
|
|
141
148
|
def _resolve_variable_owner_state(self) -> ExecutorState:
|
|
142
149
|
if self.parent_process_id is not None and self.shared_state_dicts is not None:
|
|
143
150
|
parent_state = self.shared_state_dicts.get(self.parent_process_id)
|
|
@@ -157,7 +164,7 @@ class ExecutionContext:
|
|
|
157
164
|
normalized[key] = {"value": payload.get("value"), "type": var_type}
|
|
158
165
|
return normalized
|
|
159
166
|
|
|
160
|
-
def eval_value(self, value: Any
|
|
167
|
+
def eval_value(self, value: Any):
|
|
161
168
|
"""
|
|
162
169
|
표현식 평가의 단일 진입점.
|
|
163
170
|
읽기는 매번 owner state에서 최신값을, 쓰기(aa = 3, aa += 1)는 owner state까지 반영한다.
|
|
@@ -171,8 +178,9 @@ class ExecutionContext:
|
|
|
171
178
|
result = eval_value(
|
|
172
179
|
value,
|
|
173
180
|
variables=self.variables,
|
|
174
|
-
get_global_variable=self.
|
|
175
|
-
get_var=self.get_var
|
|
181
|
+
get_global_variable=self.global_variable_getter(value),
|
|
182
|
+
get_var=self.get_var,
|
|
183
|
+
update_var=self.update_var,
|
|
176
184
|
on_write=lambda name, scope: (
|
|
177
185
|
written_global if scope == "global" else written_local
|
|
178
186
|
).add(name),
|
|
@@ -251,32 +259,80 @@ class ExecutionContext:
|
|
|
251
259
|
if category is None or robot_model is None:
|
|
252
260
|
return None
|
|
253
261
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
fn = self.get_sdk_function("rb_manipulate_sdk.get_data.get_variable")
|
|
262
|
+
if category not in ("manipulate", "amr"):
|
|
263
|
+
return None
|
|
257
264
|
|
|
258
|
-
|
|
265
|
+
res = self._fetch_core_variable(category, robot_model, var_name)
|
|
266
|
+
if res is None:
|
|
267
|
+
raise RuntimeError(f"Failed to get global variable: {var_name}")
|
|
259
268
|
|
|
260
|
-
|
|
261
|
-
raise RuntimeError(f"Failed to get global variable: {var_name}")
|
|
269
|
+
self.update_global_variables({var_name: res})
|
|
262
270
|
|
|
263
|
-
|
|
271
|
+
return res
|
|
264
272
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
273
|
+
def global_variable_getter(self, value: Any) -> Callable[[str], Any]:
|
|
274
|
+
"""RB_ 이름이 여러 개면 한 번에 선조회한 값을 쓰는 getter. 못 읽은 이름은 개별 조회."""
|
|
275
|
+
prefetched = self._prefetch_global_variables(value)
|
|
276
|
+
if not prefetched:
|
|
277
|
+
return self.get_global_variable
|
|
269
278
|
|
|
270
|
-
|
|
279
|
+
return lambda name: (
|
|
280
|
+
prefetched[name] if name in prefetched else self.get_global_variable(name)
|
|
281
|
+
)
|
|
271
282
|
|
|
272
|
-
|
|
273
|
-
|
|
283
|
+
def _prefetch_global_variables(self, value: Any) -> dict[str, Any]:
|
|
284
|
+
"""RB_ 이름 2개 이상이면 get_core_data_multi 로 한 번에 읽는다. 실패하면 {}."""
|
|
285
|
+
names = collect_rb_globals(value)
|
|
286
|
+
if len(names) < 2:
|
|
287
|
+
return {}
|
|
274
288
|
|
|
275
|
-
|
|
289
|
+
robot_model = self.state_dict.get("robot_model", None)
|
|
290
|
+
category = self.state_dict.get("category", None)
|
|
291
|
+
if category is None or robot_model is None:
|
|
292
|
+
return {}
|
|
276
293
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
294
|
+
fetched = self._fetch_core_variables_multi(category, robot_model, names)
|
|
295
|
+
if not fetched:
|
|
296
|
+
return {}
|
|
297
|
+
|
|
298
|
+
self.update_global_variables(fetched)
|
|
299
|
+
return fetched
|
|
300
|
+
|
|
301
|
+
def _fetch_core_variables_multi(self, category: Any, robot_model: Any, names: list[str]) -> dict[str, Any]:
|
|
302
|
+
"""get_core_data_multi 1회. SDK 에 없거나(구버전/amr) core 가 안 띄웠으면 {} → 개별 조회.
|
|
303
|
+
queryable 이 없으면 ZenohNoReply 가 즉시 나므로 미지원을 기억할 필요 없다."""
|
|
304
|
+
if category == "manipulate":
|
|
305
|
+
fn = self.get_sdk_function("rb_manipulate_sdk.get_data.get_variables")
|
|
306
|
+
elif category == "amr":
|
|
307
|
+
return {} # amr 은 아직 multi 없음
|
|
308
|
+
else:
|
|
309
|
+
return {}
|
|
310
|
+
if fn is None:
|
|
311
|
+
return {}
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
res = fn(robot_model=robot_model, names=names)
|
|
315
|
+
except Exception:
|
|
316
|
+
return {}
|
|
317
|
+
|
|
318
|
+
if not isinstance(res, dict):
|
|
319
|
+
return {}
|
|
320
|
+
|
|
321
|
+
# valid=0(None) 은 캐시하지 않고 개별 조회로 넘긴다
|
|
322
|
+
return {name: value for name, value in res.items() if value is not None}
|
|
323
|
+
|
|
324
|
+
def _fetch_core_variable(self, category: Any, robot_model: Any, var_name: str) -> Any:
|
|
325
|
+
"""대상 로봇 core(get_core_data)에서 RB_ 변수 라이브 조회. 다른 category/실패 시 None."""
|
|
326
|
+
if robot_model is None:
|
|
327
|
+
return None
|
|
328
|
+
if category == "manipulate":
|
|
329
|
+
fn = self.get_sdk_function("rb_manipulate_sdk.get_data.get_variable")
|
|
330
|
+
return fn(robot_model=robot_model, name=var_name) if fn is not None else None
|
|
331
|
+
if category == "amr":
|
|
332
|
+
# SLAMNAV2랑 얘기해서 추가해야함 (RB_로 시작하는 global variable)
|
|
333
|
+
fn = self.get_sdk_function("rb_amr_sdk.status.get_variable")
|
|
334
|
+
return fn(robot_model, var_name) if fn is not None else None
|
|
335
|
+
return None
|
|
280
336
|
|
|
281
337
|
def merge_variable_meta(self, name: str, meta: dict[str, Any]) -> None:
|
|
282
338
|
"""변수 entry 의 meta 에 merge. value 는 건드리지 않는다 (dict 값은 얕게 merge)."""
|
|
@@ -314,8 +370,17 @@ class ExecutionContext:
|
|
|
314
370
|
continue
|
|
315
371
|
merge_variable_entries(sd, variables, meta_by_name=meta_by_name)
|
|
316
372
|
|
|
317
|
-
def get_var(
|
|
373
|
+
def get_var(
|
|
374
|
+
self, pid_or_robot_model: str, var_name: Any = _MISSING, default: Any = _MISSING
|
|
375
|
+
) -> Any:
|
|
376
|
+
"""get_var(name[, default=]) = 자기 변수, get_var(pid|robot_model|plugin_id, name[, default])."""
|
|
377
|
+
if var_name is _MISSING:
|
|
378
|
+
return self._read_state_var(
|
|
379
|
+
self._resolve_variable_owner_state(), pid_or_robot_model, default, "self"
|
|
380
|
+
)
|
|
381
|
+
|
|
318
382
|
all_dicts = self.shared_state_dicts or {}
|
|
383
|
+
pid_or_robot_model = self.program_aliases.get(pid_or_robot_model, pid_or_robot_model)
|
|
319
384
|
|
|
320
385
|
# pid 직접 매치 우선, 없으면 robot_model로 탐색
|
|
321
386
|
target_state = all_dicts.get(pid_or_robot_model)
|
|
@@ -338,17 +403,77 @@ class ExecutionContext:
|
|
|
338
403
|
return default
|
|
339
404
|
raise RuntimeError(f"get_var: process '{pid_or_robot_model}' not found")
|
|
340
405
|
|
|
341
|
-
|
|
342
|
-
|
|
406
|
+
return self._read_state_var(target_state, var_name, default, pid_or_robot_model)
|
|
407
|
+
|
|
408
|
+
def _read_state_var(self, state: Any, var_name: Any, default: Any, label: str) -> Any:
|
|
409
|
+
# RB_ 는 core 라이브 조회 우선, 실패 시 state 캐시 폴백 (safe_eval_expr 와 동일)
|
|
410
|
+
live_err: Exception | None = None
|
|
411
|
+
if isinstance(var_name, str) and var_name.startswith("RB_"):
|
|
412
|
+
try:
|
|
413
|
+
live = self._fetch_core_variable(
|
|
414
|
+
state.get("category"), state.get("robot_model"), var_name
|
|
415
|
+
)
|
|
416
|
+
except Exception as e: # noqa: BLE001
|
|
417
|
+
live, live_err = None, e
|
|
418
|
+
if live is not None:
|
|
419
|
+
return live
|
|
420
|
+
|
|
421
|
+
entry = dict(state.get("variables", {})).get(var_name)
|
|
343
422
|
if entry is None:
|
|
344
423
|
if default is not _MISSING:
|
|
345
424
|
return default
|
|
346
425
|
raise RuntimeError(
|
|
347
|
-
f"get_var: variable '{var_name}' not found in '{
|
|
348
|
-
)
|
|
426
|
+
f"get_var: variable '{var_name}' not found in '{label}'"
|
|
427
|
+
) from live_err
|
|
349
428
|
|
|
350
429
|
return entry.get("value") if isinstance(entry, dict) else entry
|
|
351
430
|
|
|
431
|
+
def update_var(self, target: Any, variables: dict[str, Any] | None = None) -> None:
|
|
432
|
+
"""update({...}) = 자기, update(pid|robot_model|plugin_id, {...}) = 다른 프로그램.
|
|
433
|
+
|
|
434
|
+
plugin_id 는 플러그인 set_variable 과 동일(RBP_ 만, 전체 main + writer 기록).
|
|
435
|
+
"""
|
|
436
|
+
if variables is None:
|
|
437
|
+
self.update_local_variables(target)
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
variables = dict(variables)
|
|
441
|
+
all_dicts = self.shared_state_dicts or {}
|
|
442
|
+
target = self.program_aliases.get(target, target)
|
|
443
|
+
own_pid = (
|
|
444
|
+
self.parent_process_id if self.parent_process_id in all_dicts else self.process_id
|
|
445
|
+
)
|
|
446
|
+
active_mains = [
|
|
447
|
+
pid
|
|
448
|
+
for pid, sd in all_dicts.items()
|
|
449
|
+
if sd.get("parent_process_id") is None and sd.get("state") in VARIABLE_TARGET_STATES
|
|
450
|
+
]
|
|
451
|
+
meta_by_name: dict[str, dict[str, Any]] | None = None
|
|
452
|
+
|
|
453
|
+
if target in all_dicts:
|
|
454
|
+
parent = all_dicts[target].get("parent_process_id")
|
|
455
|
+
pids = [parent if parent in all_dicts else target]
|
|
456
|
+
elif any(sd.get("robot_model") == target for sd in all_dicts.values()):
|
|
457
|
+
pids = [pid for pid in active_mains if all_dicts[pid].get("robot_model") == target]
|
|
458
|
+
else:
|
|
459
|
+
bad = [name for name in variables if not str(name).startswith("RBP_")]
|
|
460
|
+
if bad:
|
|
461
|
+
raise ValueError(
|
|
462
|
+
f"update: '{target}' is not a running robot model; "
|
|
463
|
+
f"plugin variable must start with 'RBP_': {bad}"
|
|
464
|
+
)
|
|
465
|
+
pids = active_mains
|
|
466
|
+
meta_by_name = {name: {"writers": {target: v}} for name, v in variables.items()}
|
|
467
|
+
|
|
468
|
+
for pid in pids:
|
|
469
|
+
if pid == own_pid:
|
|
470
|
+
# 자기 트리는 캐시/dirty 경로로 즉시 반영
|
|
471
|
+
self.update_local_variables(variables)
|
|
472
|
+
for name, meta in (meta_by_name or {}).items():
|
|
473
|
+
self.merge_variable_meta(name, meta)
|
|
474
|
+
else:
|
|
475
|
+
merge_variable_entries(all_dicts[pid], variables, meta_by_name=meta_by_name)
|
|
476
|
+
|
|
352
477
|
def _ensure_sdk_roots(self):
|
|
353
478
|
if self._sdk_roots is not None:
|
|
354
479
|
return
|
|
@@ -837,6 +962,10 @@ class ExecutionContext:
|
|
|
837
962
|
with contextlib.suppress(Exception):
|
|
838
963
|
RBBaseSDK.close_all_for_pid()
|
|
839
964
|
|
|
965
|
+
# AMR 은 아직 Python SDK 라 C++ 레지스트리가 아니라 Python RBBaseSDK._instances 에 있다.
|
|
966
|
+
with contextlib.suppress(Exception):
|
|
967
|
+
RBAmrSDK.close_all_for_pid()
|
|
968
|
+
|
|
840
969
|
def close(self):
|
|
841
970
|
"""안전하게 close"""
|
|
842
971
|
self._safe_close_sdk()
|
|
@@ -114,6 +114,24 @@ def _build_executor_state(
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
|
|
117
|
+
def _declared_variables(step: Step | None) -> dict[str, dict[str, Any]]:
|
|
118
|
+
"""트리의 set_variables 스텝 init_value 수집."""
|
|
119
|
+
out: dict[str, dict[str, Any]] = {}
|
|
120
|
+
|
|
121
|
+
def walk(s: Any):
|
|
122
|
+
if getattr(s, "disabled", False):
|
|
123
|
+
return
|
|
124
|
+
if getattr(s, "func_name", None) == "rb_program_sdk.set_variables":
|
|
125
|
+
for v in (getattr(s, "args", None) or {}).get("variables") or []:
|
|
126
|
+
if isinstance(v, dict) and isinstance(v.get("name"), str):
|
|
127
|
+
out.setdefault(v["name"], {"value": v.get("init_value"), "type": "local"})
|
|
128
|
+
for c in getattr(s, "children", None) or []:
|
|
129
|
+
walk(c)
|
|
130
|
+
|
|
131
|
+
walk(step)
|
|
132
|
+
return out
|
|
133
|
+
|
|
134
|
+
|
|
117
135
|
def _apply_executor_state(state_dict: MutableMapping[str, Any], state: ExecutorState):
|
|
118
136
|
for key, value in state.items():
|
|
119
137
|
state_dict[key] = value
|
|
@@ -245,12 +263,19 @@ def _execute_tree_in_process(
|
|
|
245
263
|
state_dict["state"] = RB_Flow_Manager_ProgramState.ERROR
|
|
246
264
|
state_dict["error"] = msg
|
|
247
265
|
|
|
248
|
-
def
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
266
|
+
def mark_stopped(step_id: str):
|
|
267
|
+
state_dict["state"] = RB_Flow_Manager_ProgramState.STOPPED
|
|
268
|
+
state_dict["stop_requested"] = True
|
|
269
|
+
# 이미 알려진 정지(부모 stop()/ctx.stop())는 되쏘지 않는다
|
|
270
|
+
if ctx is not None and not ctx.stop_event.is_set():
|
|
252
271
|
ctx.emit_stop(step_id)
|
|
253
|
-
|
|
272
|
+
|
|
273
|
+
def set_error_or_stop(step_id: str, msg: str):
|
|
274
|
+
if "Execution stopped by user" in msg:
|
|
275
|
+
mark_stopped(step_id)
|
|
276
|
+
return
|
|
277
|
+
set_error(msg)
|
|
278
|
+
if ctx is not None:
|
|
254
279
|
ctx.emit_error(step_id, RuntimeError(msg))
|
|
255
280
|
|
|
256
281
|
def init_state():
|
|
@@ -519,15 +544,11 @@ def _execute_tree_in_process(
|
|
|
519
544
|
|
|
520
545
|
except StopExecution:
|
|
521
546
|
# stop 요청 상태로 마킹 (post_step는 정책상 실행 가능)
|
|
522
|
-
|
|
523
|
-
state_dict["stop_requested"] = True
|
|
524
|
-
if ctx is not None:
|
|
525
|
-
ctx.emit_stop(step.step_id)
|
|
547
|
+
mark_stopped(step.step_id)
|
|
526
548
|
|
|
527
549
|
except (RuntimeError, ValueError) as e:
|
|
528
550
|
# post_step 유무와 무관하게 항상 ERROR 로 표시한다. STOPPED 는 이미 그렇다.
|
|
529
|
-
|
|
530
|
-
emit_error_or_stop(step.step_id, str(e))
|
|
551
|
+
set_error_or_stop(step.step_id, str(e))
|
|
531
552
|
|
|
532
553
|
except JumpToStepException:
|
|
533
554
|
# 메인 루프 바깥으로 점프 예외가 올라오는 경우는 무시
|
|
@@ -548,14 +569,10 @@ def _execute_tree_in_process(
|
|
|
548
569
|
pass
|
|
549
570
|
|
|
550
571
|
except StopExecution:
|
|
551
|
-
|
|
552
|
-
state_dict["stop_requested"] = True
|
|
553
|
-
if ctx is not None:
|
|
554
|
-
ctx.emit_stop(step.step_id)
|
|
572
|
+
mark_stopped(step.step_id)
|
|
555
573
|
|
|
556
574
|
except (RuntimeError, ValueError) as e:
|
|
557
|
-
|
|
558
|
-
emit_error_or_stop(step.step_id, str(e))
|
|
575
|
+
set_error_or_stop(step.step_id, str(e))
|
|
559
576
|
|
|
560
577
|
except JumpToStepException as e:
|
|
561
578
|
raise RuntimeError("JumpToStep: Post program tree is not allowed.") from e
|
|
@@ -947,6 +964,32 @@ class ScriptExecutor:
|
|
|
947
964
|
if self.controller is not None:
|
|
948
965
|
self.controller.on_all_resume()
|
|
949
966
|
|
|
967
|
+
def _apply_stop_transition(
|
|
968
|
+
self,
|
|
969
|
+
process_id: str,
|
|
970
|
+
*,
|
|
971
|
+
step_id: str | None,
|
|
972
|
+
sub_task: tuple[str | None, str | None],
|
|
973
|
+
):
|
|
974
|
+
self.state_dicts[process_id]["state"] = RB_Flow_Manager_ProgramState.STOPPED
|
|
975
|
+
self.state_dicts[process_id]["ignore_pause"] = False
|
|
976
|
+
|
|
977
|
+
sub_task_id, sub_task_type = sub_task
|
|
978
|
+
if step_id is not None:
|
|
979
|
+
if self._on_stop is not None:
|
|
980
|
+
self._on_stop(process_id, step_id, sub_task_id, sub_task_type)
|
|
981
|
+
if self.controller is not None:
|
|
982
|
+
self.controller.on_stop(process_id, step_id, sub_task_id, sub_task_type)
|
|
983
|
+
|
|
984
|
+
if all(
|
|
985
|
+
self.state_dicts[pid].get("state") == RB_Flow_Manager_ProgramState.STOPPED
|
|
986
|
+
for pid in self.processes
|
|
987
|
+
):
|
|
988
|
+
if self._on_all_stop is not None:
|
|
989
|
+
self._on_all_stop()
|
|
990
|
+
if self.controller is not None:
|
|
991
|
+
self.controller.on_all_stop()
|
|
992
|
+
|
|
950
993
|
def _ensure_pre_done_event(self, process_id: str, pre_step: Step | None) -> EventType:
|
|
951
994
|
"""
|
|
952
995
|
pre_step 완료 신호를 만든다(이미 있으면 재사용).
|
|
@@ -1006,11 +1049,6 @@ class ScriptExecutor:
|
|
|
1006
1049
|
|
|
1007
1050
|
self._sync_state[flag] = st
|
|
1008
1051
|
|
|
1009
|
-
print(
|
|
1010
|
-
f"[SyncRegistry] Registered {process_id} to flag '{flag}', "
|
|
1011
|
-
f"parties_next: {st['parties_next']}"
|
|
1012
|
-
)
|
|
1013
|
-
|
|
1014
1052
|
return flags
|
|
1015
1053
|
|
|
1016
1054
|
def _unregister_process_sync_flags(self, process_id: str):
|
|
@@ -1662,8 +1700,15 @@ class ScriptExecutor:
|
|
|
1662
1700
|
self.state_dicts[process_id]["state"] = RB_Flow_Manager_ProgramState.RUNNING
|
|
1663
1701
|
self.state_dicts[process_id]["current_step_id"] = None
|
|
1664
1702
|
self.state_dicts[process_id]["ignore_pause"] = False
|
|
1665
|
-
#
|
|
1666
|
-
self.state_dicts[process_id]["variables"] =
|
|
1703
|
+
# 이전 실행 캐시 제거. main 은 Variables 스텝 init_value 선등록 (Begin 전에도 타 프로세스가 읽게)
|
|
1704
|
+
self.state_dicts[process_id]["variables"] = (
|
|
1705
|
+
{} if parent_process_id is not None else _declared_variables(step)
|
|
1706
|
+
)
|
|
1707
|
+
# P_0, P_1, ... → process_id (main task 순서). 나중에 spawn 되는 자식은 부모 것 상속
|
|
1708
|
+
aliases = args.get("program_aliases")
|
|
1709
|
+
if not aliases and parent_process_id is not None:
|
|
1710
|
+
aliases = self._state_or_empty(parent_process_id).get("program_aliases")
|
|
1711
|
+
self.state_dicts[process_id]["program_aliases"] = dict(aliases or {"P_0": process_id})
|
|
1667
1712
|
|
|
1668
1713
|
|
|
1669
1714
|
if invoke_callbacks:
|
|
@@ -1763,6 +1808,7 @@ class ScriptExecutor:
|
|
|
1763
1808
|
batch_group_id = f"batch-{int(time.time() * 1000)}-{self._run_generation + 1}"
|
|
1764
1809
|
parent_pids = [arg["process_id"] for arg in batch if arg.get("parent_process_id") is None]
|
|
1765
1810
|
self._batch_group_parents[batch_group_id] = set(parent_pids)
|
|
1811
|
+
program_aliases = {f"P_{i}": pid for i, pid in enumerate(parent_pids)}
|
|
1766
1812
|
self._batch_parent_wait_round[batch_group_id] = {}
|
|
1767
1813
|
self._batch_group_released_round[batch_group_id] = 0
|
|
1768
1814
|
self._batch_group_child_templates[batch_group_id] = {}
|
|
@@ -1789,6 +1835,7 @@ class ScriptExecutor:
|
|
|
1789
1835
|
pid = arg["process_id"]
|
|
1790
1836
|
arg["batch_group_id"] = batch_group_id
|
|
1791
1837
|
arg["batch_round_sync_parent"] = True
|
|
1838
|
+
arg["program_aliases"] = program_aliases
|
|
1792
1839
|
if arg.get("parent_process_id") is not None:
|
|
1793
1840
|
arg["batch_round_sync_parent"] = False
|
|
1794
1841
|
# 배치 자식은 라운드마다 재생성되므로 repeat_count는 1회로 고정한다.
|
|
@@ -2027,8 +2074,8 @@ class ScriptExecutor:
|
|
|
2027
2074
|
)
|
|
2028
2075
|
|
|
2029
2076
|
elif evt_type == "stop":
|
|
2030
|
-
|
|
2031
|
-
self.
|
|
2077
|
+
# 워커가 낸 정지(HALT 스텝 등). 부모 stop() 발 정지는 워커가 되쏘지 않는다
|
|
2078
|
+
self._apply_stop_transition(pid, step_id=step_id, sub_task=(sub_task_id, sub_task_type))
|
|
2032
2079
|
|
|
2033
2080
|
elif evt_type == "round_wait":
|
|
2034
2081
|
group_id = evt.get("group_id")
|
|
@@ -2693,28 +2740,14 @@ class ScriptExecutor:
|
|
|
2693
2740
|
|
|
2694
2741
|
self.pause_events[process_id].clear()
|
|
2695
2742
|
|
|
2696
|
-
step_id = self.state_dicts[process_id].get("current_step_id")
|
|
2697
|
-
self.state_dicts[process_id]["state"] = RB_Flow_Manager_ProgramState.STOPPED
|
|
2698
|
-
self.state_dicts[process_id]["ignore_pause"] = False
|
|
2699
|
-
|
|
2700
2743
|
self.stop_events[process_id].set()
|
|
2701
2744
|
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
if all(
|
|
2711
|
-
self.state_dicts[pid].get("state") == RB_Flow_Manager_ProgramState.STOPPED
|
|
2712
|
-
for pid in self.processes
|
|
2713
|
-
):
|
|
2714
|
-
if self._on_all_stop is not None:
|
|
2715
|
-
self._on_all_stop()
|
|
2716
|
-
if self.controller is not None:
|
|
2717
|
-
self.controller.on_all_stop()
|
|
2745
|
+
step_id = self.state_dicts[process_id].get("current_step_id")
|
|
2746
|
+
self._apply_stop_transition(
|
|
2747
|
+
process_id,
|
|
2748
|
+
step_id=step_id,
|
|
2749
|
+
sub_task=self._current_sub_task_info(process_id),
|
|
2750
|
+
)
|
|
2718
2751
|
|
|
2719
2752
|
print("Process stopped", flush=True)
|
|
2720
2753
|
|
|
@@ -420,7 +420,7 @@ class Step:
|
|
|
420
420
|
if self.done_script is not None:
|
|
421
421
|
print(f"[{ctx.process_id}] executing done_script for step '{self.name}'", flush=True)
|
|
422
422
|
if isinstance(self.done_script, str):
|
|
423
|
-
ctx.eval_value(self.done_script
|
|
423
|
+
ctx.eval_value(self.done_script)
|
|
424
424
|
else:
|
|
425
425
|
self.done_script()
|
|
426
426
|
|
|
@@ -454,6 +454,9 @@ class Step:
|
|
|
454
454
|
eval_args = {}
|
|
455
455
|
|
|
456
456
|
try:
|
|
457
|
+
# args/variable 의 RB_ 전역 변수를 한 번에 선조회 (core 쿼리 1번)
|
|
458
|
+
get_global_variable = ctx.global_variable_getter((args, self.variable))
|
|
459
|
+
|
|
457
460
|
for k, v in args.items():
|
|
458
461
|
if self.not_ast_eval or k.startswith("_"):
|
|
459
462
|
eval_args[k] = v
|
|
@@ -461,7 +464,7 @@ class Step:
|
|
|
461
464
|
eval_args[k] = eval_value(
|
|
462
465
|
v,
|
|
463
466
|
variables=ctx.variables,
|
|
464
|
-
get_global_variable=
|
|
467
|
+
get_global_variable=get_global_variable,
|
|
465
468
|
get_var=ctx.get_var,
|
|
466
469
|
)
|
|
467
470
|
|
|
@@ -483,7 +486,7 @@ class Step:
|
|
|
483
486
|
v = eval_value(
|
|
484
487
|
v,
|
|
485
488
|
variables=ctx.variables,
|
|
486
|
-
get_global_variable=
|
|
489
|
+
get_global_variable=get_global_variable,
|
|
487
490
|
get_var=ctx.get_var,
|
|
488
491
|
)
|
|
489
492
|
|
|
@@ -807,7 +810,7 @@ class RepeatStep(Step):
|
|
|
807
810
|
elif self.while_cond is not None:
|
|
808
811
|
if isinstance(self.while_cond, str):
|
|
809
812
|
while landing_target_step_id is not None or ctx.eval_value(
|
|
810
|
-
self.while_cond
|
|
813
|
+
self.while_cond
|
|
811
814
|
):
|
|
812
815
|
try:
|
|
813
816
|
self._pre_execute(ctx, is_skip=landing_is_skip)
|
|
@@ -860,7 +863,7 @@ class RepeatStep(Step):
|
|
|
860
863
|
landing_is_skip = False
|
|
861
864
|
continue
|
|
862
865
|
if isinstance(self.do_while, str):
|
|
863
|
-
if not ctx.eval_value(self.do_while
|
|
866
|
+
if not ctx.eval_value(self.do_while):
|
|
864
867
|
break
|
|
865
868
|
else:
|
|
866
869
|
if not self.do_while(ctx):
|
|
@@ -1030,7 +1033,7 @@ class ConditionStep(Step):
|
|
|
1030
1033
|
parent_switch_value = ctx.lookup("switch_value")
|
|
1031
1034
|
condition_expr = condition_expr.replace("$parent.switch_value", parent_switch_value)
|
|
1032
1035
|
|
|
1033
|
-
if ctx.eval_value(condition_expr
|
|
1036
|
+
if ctx.eval_value(condition_expr):
|
|
1034
1037
|
post_execute_and_execute_children()
|
|
1035
1038
|
return
|
|
1036
1039
|
|
|
@@ -1692,11 +1695,6 @@ class SyncStep(Step):
|
|
|
1692
1695
|
state["arrived"] = arrived
|
|
1693
1696
|
ctx.sync_state[flag] = state
|
|
1694
1697
|
|
|
1695
|
-
print(
|
|
1696
|
-
f"[Sync {flag}] {pid} arrived ({arrived}/{parties_cur}) phase={current_phase}",
|
|
1697
|
-
flush=True,
|
|
1698
|
-
)
|
|
1699
|
-
|
|
1700
1698
|
# 마지막 도착자
|
|
1701
1699
|
if arrived >= parties_cur:
|
|
1702
1700
|
# 마지막 도착자
|
|
@@ -1734,9 +1732,6 @@ class SyncStep(Step):
|
|
|
1734
1732
|
|
|
1735
1733
|
# phase가 넘어갔으면 해제
|
|
1736
1734
|
if updated_phase > current_phase:
|
|
1737
|
-
print(
|
|
1738
|
-
f"[Sync {flag}] {pid} released from phase {current_phase}", flush=True
|
|
1739
|
-
)
|
|
1740
1735
|
break
|
|
1741
1736
|
|
|
1742
1737
|
# 중간에 parties가 줄어서(프로세스 제거) 프로세스가 사실상 마지막이 된 경우 처리
|
|
@@ -1757,17 +1752,9 @@ class SyncStep(Step):
|
|
|
1757
1752
|
updated["parties_next"] = next_parties
|
|
1758
1753
|
ctx.sync_state[flag] = updated
|
|
1759
1754
|
|
|
1760
|
-
print(
|
|
1761
|
-
f"[Sync {flag}] {pid} becoming last due to parties reduction, "
|
|
1762
|
-
f"phase {updated_phase} -> {updated_phase + 1}, "
|
|
1763
|
-
f"parties: {updated_parties_cur} -> {next_parties}",
|
|
1764
|
-
flush=True,
|
|
1765
|
-
)
|
|
1766
1755
|
ctx.sync_cond.notify_all()
|
|
1767
1756
|
break
|
|
1768
1757
|
|
|
1769
|
-
print(f"[Sync {flag}] {pid} exiting sync", flush=True)
|
|
1770
|
-
|
|
1771
1758
|
finally:
|
|
1772
1759
|
if acquired:
|
|
1773
1760
|
ctx.sync_cond.release()
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rainbow-rb-flow-manager
|
|
3
|
-
Version: 0.1.2.
|
|
3
|
+
Version: 0.1.2.dev4
|
|
4
4
|
Summary: Rainbow Python Flow Manager
|
|
5
5
|
Author-email: Derek <dfd1123@rainbow-robotics.com>
|
|
6
6
|
Requires-Python: <3.14,>=3.11
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: rainbow-rb-utils==0.1.2.
|
|
9
|
-
Requires-Dist: rainbow-rb-zenoh==0.1.2.
|
|
10
|
-
Requires-Dist: rainbow-rb-flat-buffers==0.1.2.
|
|
11
|
-
Requires-Dist: rainbow-rb-schemas==0.1.2.
|
|
12
|
-
Requires-Dist: rainbow-rb-sdk==0.1.2.
|
|
13
|
-
Requires-Dist: rainbow-rb-log==0.1.2.
|
|
8
|
+
Requires-Dist: rainbow-rb-utils==0.1.2.dev4
|
|
9
|
+
Requires-Dist: rainbow-rb-zenoh==0.1.2.dev4
|
|
10
|
+
Requires-Dist: rainbow-rb-flat-buffers==0.1.2.dev4
|
|
11
|
+
Requires-Dist: rainbow-rb-schemas==0.1.2.dev4
|
|
12
|
+
Requires-Dist: rainbow-rb-sdk==0.1.2.dev4
|
|
13
|
+
Requires-Dist: rainbow-rb-log==0.1.2.dev4
|
|
14
14
|
|
|
15
15
|
# rb_flow_manager 사용 설명서
|
|
16
16
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|