Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-cvcgl-wasm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ jobs:
-DCVC_ENABLE_OPENMP=OFF -DDISABLE_CGAL=ON -DCVC_USING_HDF5=OFF \
-DCVC_USING_IMOD_MRC=OFF -DCVC_ENABLE_IMAGEMAGICK=ON -DCVC_ENABLE_FFTW=OFF \
-DCVC_FFT_PROVIDER=none -DCVC_ENABLE_ASSIMP=ON \
-DCVC_ENABLE_MESHER=OFF -DCVC_ENABLE_SDF=ON -DCVC_STATE_EXEC=ON \
-DCVC_ENABLE_MESHER=OFF -DCVC_ENABLE_SDF=ON \
-DCVC_BUILD_CVCGL=ON -DCVC_BUILD_EXAMPLES=OFF -DCVC_WASM_PTHREADS=ON
cmake --build build-wasm --target cvc cvcGL -j"$(nproc)"
cmake --install build-wasm
Expand Down
26 changes: 1 addition & 25 deletions bindings/pycvc/pycvc_exec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@

#include "pycvc_exec.h"

#include <stdexcept>

#ifdef CVC_STATE_EXEC

#include <chrono>
#include <condition_variable>
#include <cvc/core/app.h>
Expand All @@ -23,6 +19,7 @@
#include <mutex>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <thread>
#include <variant>
Expand Down Expand Up @@ -634,24 +631,3 @@ std::string Exec::run(const std::string &src) {
}

} // namespace pycvc

#else // !CVC_STATE_EXEC

namespace pycvc {

struct Exec::ExecImpl {};

Exec::Exec(const std::shared_ptr<cvc::app> &) {
throw std::runtime_error("pycvc.Exec: this libcvc build was compiled without state_exec");
}
Exec::~Exec() = default;
void Exec::register_fn(const std::string &, PyObject *) {
throw std::runtime_error("pycvc.Exec: this libcvc build was compiled without state_exec");
}
std::string Exec::run(const std::string &) {
throw std::runtime_error("pycvc.Exec: this libcvc build was compiled without state_exec");
}

} // namespace pycvc

#endif // CVC_STATE_EXEC
3 changes: 0 additions & 3 deletions bindings/pycvc/pycvc_exec.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
// environment + scheduler over that app's state root, so registered Python
// functions and run() programs all see the same shared tree. A Python function
// registered here becomes callable from DSL source like any builtin.
//
// Guarded by CVC_STATE_EXEC (a PUBLIC compile def on the cvc target); when the
// build lacks state_exec, the ctor throws.
#pragma once

#include <memory>
Expand Down
1 change: 0 additions & 1 deletion bindings/pycvc/test_pycvc_ari.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ def _child():

ari = pycvc_gl.AriRuntime(view, cam, ui)
try:
assert pycvc_gl.AriRuntime.have_state_exec(), "expected state_exec in this build"
pinged = {"n": 0}
ari.on("ping", lambda: pinged.__setitem__("n", pinged["n"] + 1))
warnings = ari.load(os.path.join(HERE, "demo.ari"))
Expand Down
39 changes: 7 additions & 32 deletions bindings/pycvc/test_pycvc_exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,12 @@
import pycvc


def _exec(app):
"""Exec(app), or None if this build lacks state_exec."""
try:
return pycvc.Exec(app)
except Exception as e:
if "without state_exec" in str(e):
return None
raise


# ── core DSL evaluation ─────────────────────────────────────────────────


def test_arithmetic_and_forms():
app = pycvc.make_app()
ex = _exec(app)
if ex is None:
print(" skip: build has no state_exec")
return
ex = pycvc.Exec(app)
assert ex.run("(+ 2 3)") == "5"
assert ex.run("(* 6 7)") == "42"
assert ex.run("(begin (set x 5) x)") == "5"
Expand All @@ -40,9 +27,7 @@ def test_arithmetic_and_forms():

def test_parse_error_raises():
app = pycvc.make_app()
ex = _exec(app)
if ex is None:
return
ex = pycvc.Exec(app)
try:
ex.run("(begin (set x") # unbalanced
except Exception:
Expand All @@ -56,9 +41,7 @@ def test_parse_error_raises():

def test_dsl_reads_and_writes_shared_state():
app = pycvc.make_app()
ex = _exec(app)
if ex is None:
return
ex = pycvc.Exec(app)
# Python writes state; DSL reads it.
pycvc.state_set(app, "shared.x", "7")
assert ex.run('(state-get "shared.x")') == "7"
Expand All @@ -72,9 +55,7 @@ def test_dsl_reads_and_writes_shared_state():

def test_python_function_called_from_dsl():
app = pycvc.make_app()
ex = _exec(app)
if ex is None:
return
ex = pycvc.Exec(app)

ex.register_fn("py-double", lambda x: x * 2)
ex.register_fn("py-add", lambda a, b: a + b)
Expand All @@ -89,9 +70,7 @@ def test_python_function_called_from_dsl():

def test_python_dsl_function_touches_state():
app = pycvc.make_app()
ex = _exec(app)
if ex is None:
return
ex = pycvc.Exec(app)

# A Python DSL function that writes state on the shared app.
def stash(key, value):
Expand All @@ -109,9 +88,7 @@ def stash(key, value):

def test_python_exception_is_contained():
app = pycvc.make_app()
ex = _exec(app)
if ex is None:
return
ex = pycvc.Exec(app)

def boom(*_):
raise ValueError("kaboom")
Expand All @@ -130,9 +107,7 @@ def boom(*_):

def test_value_marshaling_types():
app = pycvc.make_app()
ex = _exec(app)
if ex is None:
return
ex = pycvc.Exec(app)
seen = {}

def capture(x):
Expand Down
20 changes: 7 additions & 13 deletions bindings/pycvc/test_pycvc_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,13 @@ def on_changed(self, path):

# Phase 4: a DSL program calls a Python function that reads the state we just
# wrote — Python <-> state <-> DSL, all on the same app.
ex = None
try:
ex = pycvc.Exec(app)
except Exception as e:
if "without state_exec" not in str(e):
raise
if ex is not None:
ex.register_fn("verts", lambda: int(pycvc.state_get(app, "mesh.verts")))
# (+ 0 (verts)) forces numeric context; equals the vertex count.
assert ex.run("(+ 0 (verts))") == str(surf.num_vertices())
# A DSL write is visible back in Python.
ex.run('(state-set "dsl.done" "yes")')
assert pycvc.state_get(app, "dsl.done") == "yes"
ex = pycvc.Exec(app)
ex.register_fn("verts", lambda: int(pycvc.state_get(app, "mesh.verts")))
# (+ 0 (verts)) forces numeric context; equals the vertex count.
assert ex.run("(+ 0 (verts))") == str(surf.num_vertices())
# A DSL write is visible back in Python.
ex.run('(state-set "dsl.done" "yes")')
assert pycvc.state_get(app, "dsl.done") == "yes"

# Phase 5a: drop the meshes into a scene bound to the SAME app (no cvcGL
# singleton). num_graphics reflects what we added.
Expand Down
1 change: 0 additions & 1 deletion cvcpkg/recipes/cvcgl-examples/build-wasm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ emcmake cmake -G Ninja \
-DCVC_ENABLE_ASSIMP=ON \
-DCVC_ENABLE_MESHER=OFF \
-DCVC_ENABLE_SDF=ON \
-DCVC_STATE_EXEC=OFF \
-DCVC_BUILD_CVCGL=ON \
-DCVC_BUILD_EXAMPLES=ON \
-DCVC_WASM_PTHREADS=ON \
Expand Down
5 changes: 4 additions & 1 deletion cvcpkg/recipes/cvcgl-examples/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ recipe:
# build-pages.py into share/cvcgl-examples/web/, so cvcgl-examples-web serves
# the same gallery that deploys to gh-pages. lsystem_forest dropped from the
# wasm gallery (irrelevant to navigation).
cvc_revision: 2
# Bumped 2 -> 3: build-wasm.sh no longer forces state_exec OFF (the
# CVC_STATE_EXEC option is gone; state_exec is always built), so the wasm
# gallery carries the program lanes that Ariadne apps run on.
cvc_revision: 3
maintainer: "cvcpkg group"
maintainer_email: "info@cvcpkg.org"
maintainer_url: "https://cvcpkg.org"
Expand Down
4 changes: 2 additions & 2 deletions cvcpkg/recipes/libcvc/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,9 @@ depends:
# The gRPC/xmlrpc network state-transport stack (CVC_USING_XMLRPC + CVC_ENABLE_GRPC).
# Scoped to the platforms this recipe actually BUILDS the transport on (the build matrix
# below: linux/macos/windows). The wasm-mt bundle (built separately by publish-cvcgl-wasm.yml,
# xmlrpc/grpc OFF, CVC_STATE_EXEC ON) links none of these, so declaring them for wasm made
# xmlrpc/grpc OFF) links none of these, so declaring them for wasm made
# `cvcpkg install cvc/libcvc --platform wasm-mt` fail to resolve (abseil/protobuf/grpc are not
# published for wasm). The state_exec evaluator is independent of this stack and stays on.
# published for wasm). The state_exec evaluator is independent of this stack (always built).
- name: openssl
platforms: [linux, macos, windows]
- name: c-ares
Expand Down
1 change: 0 additions & 1 deletion cvcpkg/recipes/pycvc-gl-cp312/build-wasm.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ Invoke-CvcWasmCMakeBuild -ExtraArgs @(
'-DCVC_ENABLE_ASSIMP=ON',
'-DCVC_ENABLE_MESHER=OFF',
'-DCVC_ENABLE_SDF=ON',
'-DCVC_STATE_EXEC=OFF',
'-DCVC_BUILD_CVCGL=ON',
'-DCVC_BUILD_EXAMPLES=OFF',
"-DCVC_WASM_PTHREADS=$pthreads",
Expand Down
1 change: 0 additions & 1 deletion cvcpkg/recipes/pycvc-gl-cp312/build-wasm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ emcmake cmake -G Ninja \
-DCVC_ENABLE_ASSIMP=ON \
-DCVC_ENABLE_MESHER=OFF \
-DCVC_ENABLE_SDF=ON \
-DCVC_STATE_EXEC=OFF \
-DCVC_BUILD_CVCGL=ON \
-DCVC_BUILD_EXAMPLES=OFF \
-DCVC_WASM_PTHREADS="${_PTHREADS}" \
Expand Down
9 changes: 4 additions & 5 deletions docs/ARIADNE.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ safety property. You cannot make a "read" slot perform an action, or vice versa.

Reactive slots — `visible_when`, `enabled_when` / `disabled_when`, computed `bind` /
`text`, `tooltip`, `options`, `repeat.count` — run in a long-lived stackless evaluator
(`class ReactiveEngine`, [`ariadne.cpp:385`](../src/cvc/ariadne/ariadne.cpp), compiled
under `CVC_STATE_EXEC`) over a **default-deny** environment:
(`class ReactiveEngine`, [`ariadne.cpp:385`](../src/cvc/ariadne/ariadne.cpp)) over a
**default-deny** environment:

- The value allowlist admits arithmetic, coercion, logic, and side-effect-free state
*readers* only (`kAllowed`, [`ariadne.cpp:417`](../src/cvc/ariadne/ariadne.cpp)).
Expand All @@ -91,9 +91,8 @@ Nothing that writes state, schedules, watches, sends messages, or does I/O is
reachable. An off-allowlist symbol does not error loudly — it **fails safe**: the slot
degrades to hidden / disabled / empty / last-good value and the frame records a
one-shot warning (`take_reactive_warnings`,
[`ariadne.cpp:1682`](../src/cvc/ariadne/ariadne.cpp)). When the build lacks
`CVC_STATE_EXEC` the whole engine is `#ifdef`'d out and reactive slots default to their
fail-safe *shown* state.
[`ariadne.cpp:1682`](../src/cvc/ariadne/ariadne.cpp)). state_exec is always built (there is
no build option to drop it), so the engine is always present.

**The read lane never parks.** It is bounded three ways
([`ariadne.cpp:603`](../src/cvc/ariadne/ariadne.cpp)):
Expand Down
1 change: 0 additions & 1 deletion docs/roadmap/ARIADNE_APPRUNTIME_REFACTOR.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ public:

std::vector<std::string> take_warnings();
bool reload_if_changed();
static bool have_state_exec();
static bool have_yaml();
};
}
Expand Down
24 changes: 11 additions & 13 deletions docs/roadmap/CVCGL-UI-DSL-ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -902,8 +902,8 @@ This is the heart of v0.2. Every `*_when`, `fmt`, dynamic `options`, `repeat.cou
computed `bind`, and every `on:`/`on_change:` action is a `state_exec` program over
the **same state tree the widgets bind to**.

> **Status — the read-only lane's first field, `visible_when`, is LANDED** (`cvc::ariadne`,
> `CVC_STATE_EXEC`-gated). `Widget::visible_when` is a predicate the walk re-evaluates each
> **Status — the read-only lane's first field, `visible_when`, is LANDED** (`cvc::ariadne`).
> `Widget::visible_when` is a predicate the walk re-evaluates each
> frame; falsy → the widget and its subtree are skipped (in a grid, a hidden child consumes
> **no** cell). Implementation notes that refine §4.1 below as-built:
> - **Default-deny, on mechanisms not just an allowlist.** The between-steps caps cannot see
Expand All @@ -928,18 +928,16 @@ the **same state tree the widgets bind to**.
> aggregate wall-time budget (so N reactive widgets can't together stall a frame). A step
> cap returns `done==false`; a time cap throws `evaluation_timeout`; both → fail-safe hide
> + one warning. Diagnostics are de-duplicated and drained by `Runtime::take_reactive_warnings()`.
> - **Fail-safe polarity.** A broken/over-budget predicate HIDES the widget (§4.1). The one
> deliberate carve-out: on a build **without** `CVC_STATE_EXEC` a `visible_when` widget is
> SHOWN (+ a one-time warning), not hidden — hiding every reactive widget would gut a
> minimal build. That is a build-config axis, distinct from a per-eval failure on a build
> that *has* the evaluator (which hides).
> - **Fail-safe polarity.** A broken/over-budget predicate HIDES the widget (§4.1). (An earlier
> carve-out SHOWED `visible_when` widgets on builds without state_exec; the `CVC_STATE_EXEC`
> option was removed in #493, so every build has the evaluator.)
>
> **Also LANDED — `enabled_when` / `disabled_when`** (reactive grey-out, the sibling of
> `visible_when`): a widget stays drawn but non-interactive when `enabled_when` is falsy or
> `disabled_when` is truthy. Realized through a new backend `begin_disabled()`/`end_disabled()`
> scope (non-pure no-op defaults; ImGuiBackend → `ImGui::BeginDisabled/EndDisabled`) wrapping the
> widget and its subtree. Fail-safe DISABLES on a broken predicate; a build without state_exec
> leaves it enabled (+ warns once). Same hardened engine as `visible_when`.
> widget and its subtree. Fail-safe DISABLES on a broken predicate. Same hardened engine as
> `visible_when`.
>
> **Also LANDED — computed values + `options` + `tooltip`** (on `ReactiveEngine::eval_string` /
> `eval_string_list`): a Text whose `bind` is an s-expression (starts with `(`) is a computed
Expand Down Expand Up @@ -1160,7 +1158,7 @@ other `on:tick`/`on:key` cadence questions.
`state-set` (coalesced publisher). No scheduler touched on the draw thread.
- **Action fire (during the walk):** never inline. Fast path enqueues an intent;
general path calls `execute(ast, opts)→pid`.
> **Program `on:` LANDED** (`ariadne.cpp`, `CVC_STATE_EXEC`): an `on:` value that starts with
> **Program `on:` LANDED** (`ariadne.cpp`): an `on:` value that starts with
> `(` — an s-expression, exactly like a computed `bind:`/`tooltip:` — is a **state_exec program**
> run at `Runtime::drain()`, not a bare event name. So a flag toggle / reset is **pure `.ari`**,
> no C++ handler (`on: (state-set "paused" (if …))`). It shares one bounded runner with the
Expand Down Expand Up @@ -3848,13 +3846,13 @@ points are before anything renders.
**The `init:` block — a state_exec script run on load.** An optional top-level `init:` block carries a
`cvc::state_exec` script that runs ONCE at load, for dynamic initialization (seed/compute state before the
first frame). The loader only CAPTURES the text into `LoadResult::init_script` (it has no `cvc::app` and
never runs the DSL); `cvc::ariadne::run_init(app, prefix, script, errors)` runs it — a core seam gated by
`CVC_STATE_EXEC` (state_exec is core libcvc, so no VTK), which the host calls after load and BEFORE the
never runs the DSL); `cvc::ariadne::run_init(app, prefix, script, errors)` runs it — a core seam
(state_exec is core libcvc, so no VTK), which the host calls after load and BEFORE the
first `render()`. It runs the script under a state_exec **chroot at the document prefix**, so
`(state-set "demo.agents" "256")` writes `<prefix>.demo.agents` — the same key a widget `bind: demo.agents`
resolves to (the shared "." separator), and init values win because widget/scene `read_or_seed` only fills
keys init left unset. A parse/runtime error is reported (never thrown), not fatal — init is optional
dynamic seeding, not a hard gate; `have_state_exec()` reports whether the build can run it. The run is
dynamic seeding, not a hard gate. The run is
**bounded** (a finite step + wall-clock budget on both the process and the run loop, and success requires a
`terminated` status) so a looping or blocking init script is reported rather than hanging the app at load. This is a new
concept beyond the two §4.1 state_exec lanes (per-frame read + effectful action): a run-once init lane.
Expand Down
3 changes: 2 additions & 1 deletion docs/roadmap/STATE_EXEC_PORTING_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2315,7 +2315,8 @@ Map per category:
#### Phase 7: CMake Integration + Polish

34. ✅ **CMakeLists.txt** updates — `CVC_STATE_EXEC` and
`CVC_STATE_MEMORY_MANAGER` feature-flag options (default ON).
`CVC_STATE_MEMORY_MANAGER` feature-flag options (default ON). (`CVC_STATE_EXEC`
was later removed in #493 — state_exec is always built.)
Conditional source filtering and test target guards
(`add_executable`, `target_link_libraries`, `target_compile_features`,
`gtest_discover_tests`) wrap all state_exec and memory_manager targets.
Expand Down
2 changes: 1 addition & 1 deletion docs/roadmap/STATE_EXEC_ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Full design: [STATE_EXEC_PORTING_PLAN.md](STATE_EXEC_PORTING_PLAN.md)

| Component | Status | Tests |
|-----------|--------|-------|
| `CVC_STATE_EXEC` feature flag | ✅ Done | — |
| `CVC_STATE_EXEC` feature flag | ✅ Done, later removed (#493) — always built | — |
| `CVC_STATE_MEMORY_MANAGER` feature flag | ✅ Done | — |
| Doxygen `@file`/`@brief` on all 17 headers | ✅ Done | — |
| Benchmark tests | ✅ Done | 7 |
Expand Down
9 changes: 3 additions & 6 deletions inc/cvc/ariadne/app_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
// + a cvcGL SceneAdapter + an AppRuntime. A terminal host would compose an FtxuiBackend
// + no adapter over the same AppRuntime.
//
// libpython-free (std::function, never PyObject*). The program lanes (init:/on_tick/
// on_key/on_pointer, register_verb*, the scheduler pump) are no-ops without
// CVC_STATE_EXEC; loading a YAML .ari needs the yaml build. have_state_exec()/have_yaml()
// report which are live.
// libpython-free (std::function, never PyObject*). Loading a YAML .ari needs the yaml
// build; have_yaml() reports whether it is live.
#pragma once

#include <cstdint>
Expand Down Expand Up @@ -64,7 +62,7 @@ class AppRuntime {
// inline on the drain thread, no args/return). register_async_verb OFFLOADS `work` to the
// app compute pool and posts its string result to `done_channel`; the program parks on
// (msg-recv "done_channel") and resumes under drain()'s slice budget (the nav_compute
// pattern). Both are no-ops without state_exec.
// pattern).
void register_verb(const std::string &name, std::function<void()> fn);
void register_async_verb(const std::string &name, const std::string &done_channel,
std::function<std::string()> work);
Expand Down Expand Up @@ -96,7 +94,6 @@ class AppRuntime {
// --- diagnostics / capabilities ---
std::vector<std::string> take_warnings(); // reactive read-lane diagnostics from the last render
bool reload_if_changed(); // §12.5 hot reload if a watched source changed
static bool have_state_exec(); // program lanes + verbs live?
static bool have_yaml(); // can load a YAML .ari?

private:
Expand Down
Loading
Loading