Skip to content
Open
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
4 changes: 2 additions & 2 deletions .changeset/state-machine-wasm.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
---

New package: use a machine written in Rust from TypeScript. `fromWasm` wraps a class
exported with `dunky_wasm::export_machine!` in the same `Machine` interface the TS
exported with `dunky_state_machine_wasm::export_machine!` in the same `Machine` interface the TS
engine returns, so `connect()`, `useMachine` and every target take it unchanged, next
to TS machines.

Expand All @@ -15,7 +15,7 @@ await init()
const palette = fromWasm(new PaletteMachine(commands)) // typed from the Rust types
```

The machine runs entirely in Rust, on the Rust engine (`dunky-core`), which follows the
The machine runs entirely in Rust, on the Rust engine (`dunky-state-machine`), which follows the
same spec as the TS engine. Its TS types are generated from its Rust types and inferred
by `fromWasm`, so they are never written twice. In JS a TS machine is a little faster,
because its context already lives in JS: write a machine in Rust to reuse it in Rust
Expand Down
4 changes: 2 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Zag, whose machines read props directly.)
| `packages/shared/bindings/` | Substrate-agnostic event + attr vocabulary (onPress, role, …) |
| `packages/shared/utils/` | mergeProps, composeHandlers |
| `packages/<target>/` | Hook + normalize per substrate (react, solid, native, opentui, …) |
| `crates/core/` | The Rust engine: the same spec, in Rust (`dunky-core`) |
| `crates/core/` | The Rust engine: the same spec, in Rust (`dunky-state-machine`) |
| `crates/macros/` | `#[derive(State, Event, Context)]` for Rust machines |
| `crates/wasm/` | Rust machines as JS classes (`export_machine!`) |
| `packages/wasm/` | `fromWasm`: a Rust machine behind the TS `Machine` interface |
Expand Down Expand Up @@ -160,7 +160,7 @@ This is the target shape of the repo; the boxes marked _planned_ do not exist ye
+---------------------------+ +---------------------------+
| TS engine | | Rust engine |
| packages/core | | crates/core, crates/macros|
| @dunky.dev/state-machine | | dunky-core |
| @dunky.dev/state-machine | | dunky-state-machine |
+---------------------------+ +---------------------------+
| |
| authors | authors
Expand Down
30 changes: 15 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ repository = "https://github.com/dunky-dev/state-machine"
rust-version = "1.85"

[workspace.dependencies]
dunky-core = { path = "crates/core" }
dunky-macros = { path = "crates/macros" }
dunky-wasm = { path = "crates/wasm" }
dunky-state-machine = { path = "crates/core" }
dunky-state-machine-macros = { path = "crates/macros" }
dunky-state-machine-wasm = { path = "crates/wasm" }
dunky-sandbox = { path = "crates/sandbox" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ The Rust column of the architecture diagram (`ARCHITECTURE.md`), above the engin
- `@dunky.dev/state-machine-wasm` (`packages/wasm`) holds `fromWasm`; the TS engine
stays pure TS. `crates/wasm` is Dunky's JS binding (`export_machine!`), Dunky-specific,
in this repo because its protocol and the facade change together.
- A user's crate depends on `dunky-core` + `dunky-wasm` + `wasm-bindgen` and builds its
- A user's crate depends on `dunky-state-machine` + `dunky-state-machine-wasm` + `wasm-bindgen` and builds its
own `.wasm`; each module carries its own copy of the engine.
- The binding hears changes through the engine's own `subscribe()` / `take_changes()`
and runs timers from `take_commands()`: the engine has no hook for JS.
Expand Down
2 changes: 1 addition & 1 deletion benchmark/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Dunky has two engines that follow one spec, and each has its own suite:
| Suite | What it measures | Run |
| ----------------- | ------------------------------------------------------------------------------------------------------- | -------------------------------- |
| [`ts/`](./ts) | The TS engine (`@dunky.dev/state-machine`) against XState and Zag, plus React rendering and a live demo | `pnpm benchmark` |
| [`rust/`](./rust) | The Rust engine (`dunky-core`), natively, on the same scenarios, plus a 60 fps frame budget | `cargo bench -p dunky-benchmark` |
| [`rust/`](./rust) | The Rust engine (`dunky-state-machine`), natively, on the same scenarios, plus a 60 fps frame budget | `cargo bench -p dunky-benchmark` |

The Rust rows are twins of TS rows, so a change to one engine can be measured against the
same work on the other.
4 changes: 2 additions & 2 deletions benchmark/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[package]
name = "dunky-benchmark"
description = "The Rust engine's benchmark: the TS suite's engine scenarios, run natively on dunky-core."
description = "The Rust engine's benchmark: the TS suite's engine scenarios, run natively on dunky-state-machine."
version.workspace = true
edition.workspace = true
license.workspace = true
publish = false

[dependencies]
dunky-core = { workspace = true }
dunky-state-machine = { workspace = true }

[[bench]]
name = "engine"
Expand Down
2 changes: 1 addition & 1 deletion benchmark/rust/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Rust benchmark suite

The Rust engine (`dunky-core`), measured natively. Release build, Apple M5 Pro; medians are
The Rust engine (`dunky-state-machine`), measured natively. Release build, Apple M5 Pro; medians are
the average of two runs, and the frame table's p99 and worst columns take the **higher** of
the two runs. Absolute figures vary by machine and thermal state — **run it yourself**.

Expand Down
4 changes: 2 additions & 2 deletions benchmark/rust/benches/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};

use dunky_benchmark::*;
use dunky_core::Machine;
use dunky_state_machine::Machine;

/// Counts live heap bytes (the memory rows) and allocations (the frame rows).
struct Counting;
Expand Down Expand Up @@ -203,7 +203,7 @@ fn frame_budget() {
}

fn main() {
println!("Rust engine benchmark (dunky-core, native, release build)");
println!("Rust engine benchmark (dunky-state-machine, native, release build)");

frame_budget();

Expand Down
2 changes: 1 addition & 1 deletion benchmark/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use std::cell::Cell;

use dunky_core::{ComputedKey, Config, Context, Event, Machine, Selection, State, Types};
use dunky_state_machine::{ComputedKey, Config, Context, Event, Machine, Selection, State, Types};

thread_local! {
static SINK: Cell<u64> = const { Cell::new(0) };
Expand Down
6 changes: 3 additions & 3 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "dunky-core"
name = "dunky-state-machine"
description = "Substrate-agnostic state-machine engine — the Rust port of @dunky.dev/state-machine."
version.workspace = true
edition.workspace = true
Expand All @@ -10,12 +10,12 @@ rust-version.workspace = true
[features]
default = ["derive", "serde"]
# #[derive(State, Event, Context)]
derive = ["dep:dunky-macros"]
derive = ["dep:dunky-state-machine-macros"]
# Per-field serialization (SerializeFields) for bindings that mirror context into JS.
serde = ["dep:serde"]

[dependencies]
dunky-macros = { workspace = true, optional = true }
dunky-state-machine-macros = { workspace = true, optional = true }
serde = { workspace = true, optional = true }

[dev-dependencies]
Expand Down
4 changes: 2 additions & 2 deletions crates/core/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# dunky-core
# dunky-state-machine

Dunky's state machines in Rust: the twin of the TypeScript engine,
[`@dunky.dev/state-machine`](../../packages/core). Both follow one behavior contract,
[`packages/core/SPEC.md`](../../packages/core/SPEC.md); the Rust tests in `tests/` are
ported from `packages/core/tests`. Use it in Rust programs and native bindings.

```rust
use dunky_core::{Config, Context, Event, Machine, State, Types};
use dunky_state_machine::{Config, Context, Event, Machine, State, Types};

#[derive(State, Clone, Copy, PartialEq, Eq, Debug)]
enum DialogState { Closed, Open }
Expand Down
4 changes: 2 additions & 2 deletions crates/core/SPEC.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# SPEC - `dunky-core`
# SPEC - `dunky-state-machine`

## Overview

`dunky-core` is the Rust implementation of Dunky's state machines. The TypeScript
`dunky-state-machine` is the Rust implementation of Dunky's state machines. The TypeScript
implementation is [`@dunky.dev/state-machine`](../../packages/core). They are two
engines with one behavior contract,
[`packages/core/SPEC.md`](../../packages/core/SPEC.md): flat states, run-to-completion,
Expand Down
8 changes: 4 additions & 4 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! `dunky-core` — the Rust implementation of Dunky's state machines, next to the TS one
//! `dunky-state-machine` — the Rust implementation of Dunky's state machines, next to the TS one
//! (`@dunky.dev/state-machine`).
//!
//! Both implement the same behavior contract, `packages/core/SPEC.md`; what is specific
Expand All @@ -13,8 +13,8 @@
//! - **Change mask**: [`Machine::take_changes`] reports which fields changed, so a binding
//! can mirror only those into another runtime.

// Lets the derive macros (which emit `::dunky_core::...`) work inside this crate too.
extern crate self as dunky_core;
// Lets the derive macros (which emit `::dunky_state_machine::...`) work inside this crate too.
extern crate self as dunky_state_machine;

mod broadcast;
mod compose;
Expand Down Expand Up @@ -51,7 +51,7 @@ pub use traits::{DeserializeEvent, SerializeFields};
pub use ts::TsType;

#[cfg(feature = "derive")]
pub use dunky_macros::{Context, Event, State, TsType};
pub use dunky_state_machine_macros::{Context, Event, State, TsType};

/// The `machine.init` marker name: what a boot effect or a data-reaction sees as its
/// event type (`ActionParams::event()` returns `None` for it).
Expand Down
2 changes: 1 addition & 1 deletion crates/core/src/ts.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! TypeScript types of the values a machine shows JS, so a binding can generate a Rust
//! machine's TS types from its Rust ones. The mapping follows how `dunky-wasm` moves
//! machine's TS types from its Rust ones. The mapping follows how `dunky-state-machine-wasm` moves
//! values through serde: `None` is `null`, sequences are arrays, maps are `Map`s, and
//! every number is a `number`.

Expand Down
4 changes: 2 additions & 2 deletions crates/core/tests/act.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
mod common;

use common::{Log, build};
use dunky_core::{Config, Context, Event, State, Types};
use dunky_state_machine::{Config, Context, Event, State, Types};

struct M;

Expand Down Expand Up @@ -108,7 +108,7 @@ fn composes_with_a_target_and_other_actions_in_order() {
let log: Log = Log::default();
let record = |phase: &'static str| {
let log = log.clone();
move |p: &mut dunky_core::ActionParams<'_, M>| {
move |p: &mut dunky_state_machine::ActionParams<'_, M>| {
log.push(format!("{phase}: hit={}", p.context().hit));
}
};
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
mod common;

use common::{CounterEvent, Log, build, counter};
use dunky_core::{
use dunky_state_machine::{
Action, Branch, Config, Context, Event, Guard, GuardParams, Machine, State, Types,
};

Expand Down
4 changes: 2 additions & 2 deletions crates/core/tests/after.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
mod common;

use common::{Log, build};
use dunky_core::testing::Clock;
use dunky_core::{Command, Config, Context, Event, State, Types};
use dunky_state_machine::testing::Clock;
use dunky_state_machine::{Command, Config, Context, Event, State, Types};

struct M;

Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/bare_fn_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
mod common;

use common::{Log, build};
use dunky_core::{Config, Context, Event, State, Types};
use dunky_state_machine::{Config, Context, Event, State, Types};

struct M;

Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::cell::{Cell, RefCell};
use std::rc::Rc;

use common::{Count, CounterEvent, Log, build, counter};
use dunky_core::{Connector, Subscription};
use dunky_state_machine::{Connector, Subscription};

/// A slot for a subscription made after the listener that will remove it.
type Slot = Rc<RefCell<Option<Subscription>>>;
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use std::cell::{Cell, RefCell};
use std::rc::Rc;

use dunky_core::{
use dunky_state_machine::{
ActionParams, Cleanup, Config, ConfigBuilder, Context, Event, Machine, State, Types,
};

Expand Down
4 changes: 3 additions & 1 deletion crates/core/tests/compose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ mod common;
use std::rc::Rc;

use common::{AbState, Calls, CounterEvent, Log, ab, build, counter};
use dunky_core::{Composition, Config, Event, Machine, Member, State, Subscription, Types};
use dunky_state_machine::{
Composition, Config, Event, Machine, Member, State, Subscription, Types,
};

struct Popup;

Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/computed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
mod common;

use common::{Calls, Log, build};
use dunky_core::{
use dunky_state_machine::{
ComputedKey, Config, ConfigBuilder, Context, Event, Machine, State, StateEnum, Types,
};

Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::cell::RefCell;
use std::rc::Rc;

use common::{Ab, AbEvent, AbState, Calls, Log, ab, build};
use dunky_core::{
use dunky_state_machine::{
Config, Connector, Context, Event, Machine, Reaction, State, Subscription, Types,
};

Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/derive.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Rust-only: the derive macros name events exactly like serde, so the JS side can send
//! `{ type, ... }` objects that both decoders (serde and `DeserializeEvent`) accept.

use dunky_core::{Context, DeserializeEvent, Event, EventEnum, State, TsType};
use dunky_state_machine::{Context, DeserializeEvent, Event, EventEnum, State, TsType};
use serde::{Deserialize, Serialize};

#[derive(Event, Clone, Debug, PartialEq, Deserialize)]
Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mod common;
use std::panic::{AssertUnwindSafe, catch_unwind};

use common::{Ab, AbEvent, AbState, Count, Counter, CounterState, Log, ab, build};
use dunky_core::{
use dunky_state_machine::{
ActionParams, Cleanup, Config, Context, Event, EventEnum, MACHINE_INIT, State, Types,
};

Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/guards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
mod common;

use common::{Calls, build};
use dunky_core::{Config, ConfigBuilder, Context, Event, Guard, State, Types};
use dunky_state_machine::{Config, ConfigBuilder, Context, Event, Guard, State, Types};

struct M;

Expand Down
2 changes: 1 addition & 1 deletion crates/core/tests/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
mod common;

use common::{Ab, AbEvent, AbEventKind, AbState, Calls, Log, ab, build};
use dunky_core::Config;
use dunky_state_machine::Config;

#[test]
fn is_built_stopped_and_start_boots_the_effects() {
Expand Down
4 changes: 2 additions & 2 deletions crates/core/tests/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
mod common;

use common::{Calls, Log, build};
use dunky_core::testing::Clock;
use dunky_core::{Config, Context, Event, State, Types};
use dunky_state_machine::testing::Clock;
use dunky_state_machine::{Config, Context, Event, State, Types};

struct M;

Expand Down
Loading
Loading