pub struct StateEngine {
pub arena: Rc<Arena>,
pub templates: Rc<TemplateRegistry>,
/* private fields */
}Expand description
STATE’s top-level owned state (arena storage + the compiled template set)
— review fix: arena/templates are Rc-wrapped, not Box-wrapped or
embedded by value. A SimHandle/InstanceHandle minted by
Self::allocate_instance must stay valid for the life of the
simulation run and cross the compiled-kernel FFI boundary, so the raw
pointers it stores must survive StateEngine itself being moved —
returned by value from a constructor, pushed into a Vec, or embedded in
a future root object are all ordinary things a caller does (this task’s
own regression test exercises exactly this). With arena/templates
embedded directly, any such move would silently dangle every
previously-minted handle — no compiler diagnostic, UB on the next
accessor call.
A first review fix boxed them (Box<Arena>/Box<TemplateRegistry>),
reasoning that a heap allocation’s address doesn’t change when the
(movable) Box pointer value travels with a moved StateEngine. That
address argument is correct but insufficient: cargo +nightly miri test -p sv-sim-sim instance:: still fails with Box<Arena>, because moving a
Box<T> value — including as a field of a larger struct passed by value
into a function, e.g. Box::new(engine) or returning engine from a
constructor — retags the Box’s pointee as freshly, exclusively owned
under Rust’s Stacked/Tree Borrows model (the same aliasing treatment
&mut T gets), independent of the pointee’s own address ever changing.
That invalidates every previously-derived raw pointer into it, Arena’s
own UnsafeCell-based interior mutability notwithstanding. Rc<T> does
not carry this exclusive-ownership retag-on-move semantics — it’s a
plain (refcounted) shared-ownership pointer, not a reference-like type
Miri’s retagging pass treats specially — so Rc<Arena>/
Rc<TemplateRegistry> keep the same “stable heap address, movable
container” property Box gave without the move-retag hazard. This is
consistent with (and depends on) Arena’s and TemplateRegistry’s own
mutating methods already being &self-based via UnsafeCell: Rc
cannot produce &mut access at all (short of Rc::get_mut, unused
here), so nothing about this design ever needed &mut Arena/
&mut TemplateRegistry in the first place. self.arena/self.templates
field access auto-derefs through Rc exactly as it would through a bare
field or a Box.
Task 10 (ST-152): every resource STATE owns — arena, templates,
cells (Task 8), vc (the Task 9 retrofit’s VC-tracking state; the VC
dispatch table and undo log join this list once Tasks 11/16 land) — is a
direct field of this struct, never an Option<*mut OtherGlobal>
indirection and never a free-standing process-wide global or
lazily-initialized singleton cell of any kind anywhere in this crate (this
module’s own test suite enforces the exact forbidden spellings by scanning
this crate’s state source). The only pointer any accessor/ABI function
accepts to reach any of it is SimHandle/InstanceHandle (itself
reachable only via SimHandle, per this plan’s header decision) or, for
scheduler-side callers, a &/&mut StateEngine reached however the caller
already holds one — never a second, independent “give me the arena”
pointer parameter threaded alongside it.
Task 15 (ST-033): every super::FourStateValue in the running
simulation lives in exactly one of Arena.{inline,wide_aval,wide_bval}
(or the _2s twins), reached only via a StateCellId/SlotLocation
STATE itself minted (Task 8’s alloc_cell) — no other crate/module in
this workspace holds a second copy. This is the ST-152 discipline above
applied specifically to signal values (not merely STATE’s own
resources): every read/write path this crate exposes
(SignalAccess::read/write, the accessor ABI, driver::resolve_net_step)
resolves a StateCellId down to a SlotLocation via self.cells and
indexes into self.arena’s planes directly, never into a second,
independently-populated store — see region_check:: no_second_authoritative_copy_exists_for_a_cell_read_through_two_apis for
the concrete regression proving two different read APIs observe the exact
same storage with no manual “sync” step between them.
Fields§
§arena: Rc<Arena>§templates: Rc<TemplateRegistry>Implementations§
Source§impl StateEngine
impl StateEngine
pub fn new(templates: TemplateRegistry) -> Self
Sourcepub fn new_with_mode(templates: TemplateRegistry, mode: BuildMode) -> Self
pub fn new_with_mode(templates: TemplateRegistry, mode: BuildMode) -> Self
Task 13 (ST-100, ST-101, ST-102): the only constructor that can
produce a BuildMode::TwoState StateEngine – mode is threaded
straight into Arena::new and never mutated after (see
write::StateEngine::build_mode’s own doc comment for why no setter
exists anywhere in this crate).
Sourcepub fn set_current_time(&mut self, at: SimTime)
pub fn set_current_time(&mut self, at: SimTime)
Task 8’s “how STATE learns now” header decision: called by ENGINE’s
region-eval loop once per region, before invoking any kernel/process
that might call back into commit_write. commit_write itself never
gains a time parameter – it reads this (via self.vc) instead.
Sourcepub fn set_current_region(&mut self, region: Region)
pub fn set_current_region(&mut self, region: Region)
The Region counterpart to set_current_time.
Sourcepub fn register_worker_thread(&mut self) -> WorkerSlot
pub fn register_worker_thread(&mut self) -> WorkerSlot
Task 8’s “cross-thread visibility of the VC speculative buffer”
header decision: called once by each thread before its first
commit_write (or self-called by commit_write itself on first
use, via state::write’s thread-local cache). Pushes a fresh,
individually-boxed RefCell<Vec<VcRecord>> onto self.vc’s
worker_buffers and returns its index as an opaque WorkerSlot –
each entry’s own heap allocation has a stable address for the life of
this StateEngine regardless of how many further times
worker_buffers itself grows (the same box-per-entry stability
argument TemplateRegistry already relies on for StateLayout).
SAFETY precondition (documented here, exercised for real once a
later task’s drain_committed_changes exists): this function – and
every commit_write call that appends through the slot it returns –
may run only when no other thread concurrently drains any registered
buffer; eval and commit/drain are barrier-separated phases in this
wave’s WOCB execution model, never concurrent with each other.
Sourcepub fn slot_of(
&self,
template: &TemplateKey,
symbol: SymbolKey,
) -> Option<SlotOffset>
pub fn slot_of( &self, template: &TemplateKey, symbol: SymbolKey, ) -> Option<SlotOffset>
Template-relative only (ST-129/ST-135): never adds an instance base.
Returns None — never a fabricated default SlotOffset — when
symbol is not present in template’s built StateLayout, or when
template itself was never registered (ST-037: SlotOffset values
are minted exclusively by the Task 4/6 layout builder, never
synthesized on demand by this query API).
Sourcepub fn template_size(&self, template: &TemplateKey) -> Option<(u32, u32)>
pub fn template_size(&self, template: &TemplateKey) -> Option<(u32, u32)>
(inline_words, wide_words) straight from the StateLayout (ST-135).
Sourcepub fn allocate_instance(
&mut self,
template: TemplateKey,
) -> Option<Box<InstanceHandle>>
pub fn allocate_instance( &mut self, template: TemplateKey, ) -> Option<Box<InstanceHandle>>
Looks up template_size, bulk-allocates that many words via the two
Arena bulk allocators, and returns a heap-boxed InstanceHandle
(boxed so its address is stable — Task 8 turns that address directly
into a SimHandle) whose base is disjoint from every previously
issued base for this arena (ST-067/ST-136). Returns None iff
template was never registered — never a fabricated zero-sized
allocation.
Source§impl StateEngine
impl StateEngine
Sourcepub fn allocate_class_static_singletons(
&mut self,
layouts: Vec<(ClassTemplateId, StateLayout)>,
) -> HashMap<ClassTemplateId, (InstanceId, Box<InstanceHandle>)>
pub fn allocate_class_static_singletons( &mut self, layouts: Vec<(ClassTemplateId, StateLayout)>, ) -> HashMap<ClassTemplateId, (InstanceId, Box<InstanceHandle>)>
Classes plan Task 4 (docs/superpowers/plans/2026-10-03-classes.md,
LRM 8.9): allocates one singleton segment per (ClassTemplateId, StateLayout) pair in layouts — exactly once per class template,
regardless of how many objects of it are ever created (object
creation itself, Task 5’s new()/the object heap, never allocates
one of these; it is minted once here, at elaboration time, sized
exactly the way one real module Template’s own single set of
slots already is). layouts is expected to already be deduplicated
by the caller (e.g. one entry per
crate::netlist::class_templates_with_statics result) — this
function does not itself re-check for a repeated ClassTemplateId,
the same “trust the caller’s own dedup pass” discipline
allocate_instance’s own callers already follow for TemplateKey.
For each pair, registers layout under the TemplateKey
class_static_instance mints for its ClassTemplateId, then
calls Self::allocate_instance on it immediately — the same
two-step sv-sim-bin::run already performs for a package’s own
singleton instance (the established precedent this reuses, not a
new mechanism: allocate_instance’s own base-index/SlotOffset
addressing and the SlotId/accessor-ABI read/write path are
completely unchanged by this function).
Returns the minted synthetic InstanceId (from
class_static_instance) and the singleton’s own InstanceHandle,
keyed by ClassTemplateId, for the caller to keep alive for the
rest of the run — a future task’s Class::static_prop/
obj.static_prop codegen resolves through the returned handle via a
foreign slot, exactly like a package variable access does today
(not wired by this function: nothing in this plan’s Task 4 scope yet
emits such a reference — see this plan’s own Task 4 deviations).
Source§impl StateEngine
impl StateEngine
Sourcepub fn alloc_cell(&mut self, loc: SlotLocation) -> StateCellId
pub fn alloc_cell(&mut self, loc: SlotLocation) -> StateCellId
StateCellId allocation (ST-109): appends loc to a private
Vec<SlotLocation> and returns its dense index as the new
StateCellId, so StateCellId(i) and cells[i] are always in
lockstep (no reuse/free in v1, matching the arena’s own bump-allocator
discipline).
Sourcepub fn commit_write(
&mut self,
cell: StateCellId,
new: FourStateValue,
hook: &mut dyn PreWriteHook,
) -> ChangeResult
pub fn commit_write( &mut self, cell: StateCellId, new: FourStateValue, hook: &mut dyn PreWriteHook, ) -> ChangeResult
The one write primitive (ST-109, ST-079, ST-082, ST-091, ST-155):
looks up cell’s already-allocated SlotLocation and forwards to
[commit_write_at] — see that function’s doc comment for the full
algorithm. Task 12 (ST-075): on a real change, also makes exactly one
call to Dispatch::notify_scheduler — the only call site
notify_scheduler has anywhere in this crate — supplying self.vc’s
own current_time/current_region (this plan’s header “how STATE
learns now” decision; never a parameter this method’s own signature
carries). Dispatch::notify_waveform_and_vpi is deliberately not
called here: Waveform/VPI observe only through
StateEngine::drain_committed_changes (state::gvt), reading the
VcRecord [commit_write_at] already appended above. Scheduler
wakeup is never GVT-gated — see Dispatch::notify_scheduler’s own
doc comment for why.
Sourcepub fn alloc_view_cell(
&mut self,
root: StateCellId,
window: AliasWindow,
) -> StateCellId
pub fn alloc_view_cell( &mut self, root: StateCellId, window: AliasWindow, ) -> StateCellId
The view cell of window of root (state::alias): a cell that
owns no storage, whose value is that window of root’s. One cell per
distinct (root, window). Never reset (root is).
Sourcepub fn build_mode(&self) -> BuildMode
pub fn build_mode(&self) -> BuildMode
Task 13 (ST-101): read-only accessor so a test (or, later, RUNTIME)
can confirm which mode a given StateEngine was constructed in. No
set_build_mode method exists anywhere in this crate’s public API –
BuildMode is fixed once at Arena::new (via
StateEngine::new_with_mode, state::instance) and never mutated
after, which is the structural enforcement of “no design or
implementation makes 4-state and 2-state arenas coexist at runtime”.
Source§impl StateEngine
impl StateEngine
Sourcepub fn trireg_charge(&self, cell: StateCellId) -> Option<&TriregCharge>
pub fn trireg_charge(&self, cell: StateCellId) -> Option<&TriregCharge>
Task 14 (ST-034): read-only lookup into this engine’s own
trireg_charge side table.
Sourcepub fn set_trireg_charge(&mut self, cell: StateCellId, charge: TriregCharge)
pub fn set_trireg_charge(&mut self, cell: StateCellId, charge: TriregCharge)
Task 14 (ST-034): records/replaces cell’s stored charge state. No
decay-timing behavior happens here or anywhere in this crate this
wave — see TriregCharge’s own doc comment.
Source§impl StateEngine
impl StateEngine
Sourcepub fn register_force_observed(&mut self, listener: Box<dyn ForceObserved>)
pub fn register_force_observed(&mut self, listener: Box<dyn ForceObserved>)
Registers listener to be notified (in registration order) on every
future force/release (driver::force_driver/release_driver).
No upper bound on how many listeners can be registered (unlike
Dispatch’s fixed 3-role table, ST-074) — force/release are rare,
external, out-of-band operations, not the per-event hot path ST-074’s
no-heap-growth discipline targets.
Source§impl StateEngine
impl StateEngine
Sourcepub fn snapshot(&self, range: Option<(SlotClass, u32, u32)>) -> ArenaSnapshot
pub fn snapshot(&self, range: Option<(SlotClass, u32, u32)>) -> ArenaSnapshot
ST-098: takes a snapshot of range (None = the whole arena) —
layout_hash is self.templates.signature_hash() at the moment of
the call, guarding a later Self::restore against an incompatible
StateEngine. Review fix (Task 16 r1, blocking finding 1):
build-mode-aware — branches once on self.build_mode() and captures
whichever plane-pair ({inline, wide_aval, wide_bval} for
FourState, {inline_2s, wide_2s} for TwoState) is actually the
engine’s live data at range’s word offsets (Task 13’s
alloc_inline_words/alloc_wide_words grow both plane-pairs in
lockstep at identical offsets, state::arena’s own module doc
comment, so the offsets are shared — only which plane-pair holds the
real values differs); the other plane-pair’s fields stay empty
Vecs. The captured build_mode is stamped onto the returned
snapshot so Self::restore can refuse a cross-mode restore.
§Panics
If range’s SlotClass is ForceWide — force_driver’s synthetic,
caller-invisible contribution cells (see SlotClass::ForceWide’s own
doc comment) are never part of a real elaborated signal’s storage and
are not an intended target of this API (WOCB rollback, $save/
$restart, and VPI/debugger snapshotting all operate over ordinary
Inline/Wide/Memory signals); a caller reaching this is a
programming error at the call site, the same class of failure
write_slot’s own shape-mismatch panic already documents elsewhere
in this crate, not a normal case this primitive tolerates silently.
Sourcepub fn restore(&mut self, snap: &ArenaSnapshot) -> Result<(), RewindError>
pub fn restore(&mut self, snap: &ArenaSnapshot) -> Result<(), RewindError>
ST-098: restores snap’s data back into self’s arena at snap’s
own recorded range (or the whole arena, for None) — a slice
operation for Some(range): words outside that range are left
completely untouched. Err(RewindError::LayoutMismatch) — checked
before any write — when self’s currently-registered template
set doesn’t hash the same as the one snap was taken from, when
self’s build_mode doesn’t match snap’s own (review fix, Task
16 r1, blocking finding 1 — restoring a FourState snapshot into a
TwoState engine, or vice versa, would otherwise silently target the
wrong plane-pair entirely, the identical hazard this fix closes for
snapshot/reset_to_initial), and when the target range’s own word
count doesn’t match what snap actually holds (a
same-hash-different-instance-count edge case the plan’s own
four-field ArenaSnapshot shape doesn’t separately name, but which
this same typed error class covers rather than panicking on a
length-mismatched copy_from_slice); on any mismatch, self’s
arena contents are provably unchanged — no partial corruption on the
rejected path.
Sourcepub fn reset_to_initial(&mut self)
pub fn reset_to_initial(&mut self)
ST-098 (documented alongside snapshot/restore as this same
primitive family’s third bulk operation, per the plan’s own prose):
re-blits every cell’s [CellReset] (state::instance’s
cell_reset, populated by allocate_instance) over the arena — a
declared initializer’s exact value for a slot that has one, all-X (4-
state) or 0 (2-state) for an uninitialized non-net, non-force slot,
and nothing at all for a net-backed slot (its reset state is the
emergent Z driver::resolve_net_step folds to over zero active
drivers, never a stored arena default) or a force_driver contribution
cell (review fix, Task 16 r1, blocking finding 2 — never a real
elaborated signal’s storage; see [CellReset::ForceContribution]’s
own doc comment for why blitting over one would silently corrupt a
live force). Bypasses commit_write/PreWriteHook/VcRecord
entirely (a direct write_slot/write_inline_2s/write_wide_2s
poke, the same tier of primitive Self::restore uses) — this
establishes the simulation’s actual starting state before any process
runs, not an ordinary mid-run write a scheduler/waveform/VPI listener
should observe as a “change.” Review fix (Task 16 r1, blocking
finding 1): build-mode-aware — branches once on self.build_mode()
and re-blits whichever plane-pair is this engine’s actual live data
(see this module’s top doc comment, deviation 5).
Sourcepub fn reset_to_initial_with(&mut self, x_as_zero: bool)
pub fn reset_to_initial_with(&mut self, x_as_zero: bool)
Self::reset_to_initial, optionally starting every uninitialized
4-state variable and memory row at 0 instead of X (x_as_zero): the
non-LRM --x-initial zero run option, for benches written against a
2-state simulator (Verilator’s --x-initial 0, VCS’s +vcs+initreg+0).
Nets keep their LRM starting values.
Source§impl StateEngine
impl StateEngine
Sourcepub fn new_activation_record(
&mut self,
pc: u32,
frame_word_count: u32,
) -> ActivationRecord
pub fn new_activation_record( &mut self, pc: u32, frame_word_count: u32, ) -> ActivationRecord
STATE’s one constructor for a real (non-hand-literal)
process::ActivationRecord (ST-103, ST-110): allocates
frame_word_count words from this engine’s own FrameArena and
returns a fresh record whose pc/frame are both STATE-owned
storage. pending_wait/fork_state start at their empty defaults
(WaitSpecStorage::None/None) — populating them (via
alloc_edge_terms/alloc_fork_children above) is the caller’s job
once a suspend point or fork actually occurs, not this constructor’s.
Source§impl StateEngine
impl StateEngine
Sourcepub fn alloc_event_cell(&mut self) -> StateCellId
pub fn alloc_event_cell(&mut self) -> StateCellId
ST-111: allocates a fresh event-variable cell — a real, ordinary
1-bit SlotClass::Inline cell (via super::arena::Arena::alloc_inline_word,
which zero-initializes its backing word, so the cell decodes to
logic 0/“not triggered” immediately after allocation with no
further write needed) — and records its StateCellId in
self.event_cells, the one thing that distinguishes it from an
ordinary 1-bit variable cell (see this module’s own doc comment for
why no new SlotClass variant exists for this).
Sourcepub fn trigger_event(
&mut self,
cell: StateCellId,
hook: &mut dyn PreWriteHook,
) -> ChangeResult
pub fn trigger_event( &mut self, cell: StateCellId, hook: &mut dyn PreWriteHook, ) -> ChangeResult
ST-111: the trigger pulse — an ordinary StateEngine::commit_write
(so the real VC/notify_change/Dispatch::notify_scheduler path
fires, which is exactly what wakes a process sensitive to
sched::EventTerm::NamedEventTrigger(cell) via
sched::SensitivityTable’s already-landed “any real change wakes
it” handling — see sched::sensitivity::SensitivityTable::install_term’s
NamedEventTrigger arm). Writes the logical complement of the cell’s
current value (an event cell’s own “value” carries no LRM meaning —
only the transition matters — so flipping it, rather than always
writing the same constant, guarantees commit_write’s own
old-vs-new diff always reports a real change, even across repeated
triggers with no intervening clear).
§Panics
In debug builds, if cell was never allocated via
Self::alloc_event_cell — a caller triggering an ordinary
(non-event) cell through this path is a programming error, not a
silently-tolerated case, matching this crate’s “loud, never silent”
discipline (ARCHITECTURE §7 item 9).
Sourcepub fn clear_event_pulses(&mut self)
pub fn clear_event_pulses(&mut self)
ST-111: LRM .triggered’s “valid only through the current time
step” — clears every tracked event cell back to 0, via
StateEngine::commit_write_no_hook (no VcRecord append, no
scheduler notification on the clearing edge: clearing is not itself
an LRM-observable event — see Self::commit_write_no_hook’s own
doc comment for why that primitive is the one that skips both).
Source§impl StateEngine
impl StateEngine
Sourcepub fn advance_gvt(&mut self, gvt: SimTime)
pub fn advance_gvt(&mut self, gvt: SimTime)
ST-081: a pure setter (self.vc.set_gvt_watermark(gvt)) — no
dependency on any scheduler-internal type, callable with a bare
SimTime the caller (ENGINE, later) computed however it likes. See
write::VcState’s gvt_watermark field doc comment for why this
field exists even though drain_committed_changes below takes its
own explicit up_to parameter rather than reading this field back.
Task 20 (ST-124) — open joint sizing risk with ENGINE, not bounded
by this crate alone. Every un-drained speculative VC record between
two advance_gvt calls stays buffered (write::VcState’s per-thread
buffer, Task 8/9): this is only bounded in practice if the caller
(ENGINE) advances GVT “cheaply and frequently enough” relative to the
write rate. STATE has no way to observe or enforce ENGINE’s actual
GVT-advancement cadence from inside this crate — a coarse-grained
cadence (e.g. only at major time-step boundaries) could make the
buffering cost far larger than assumed here. Tracked as an open risk
(docs/design/state.md Risks/Open Questions), not silently assumed
safe; restated in docs/CONTRACTS.md’s “Wave A — wA-state notes”
section so ENGINE’s own wave can pick it up without re-deriving it
from this doc comment.
Sourcepub fn drain_committed_changes(&mut self, up_to: SimTime) -> Vec<VcRecord>
pub fn drain_committed_changes(&mut self, up_to: SimTime) -> Vec<VcRecord>
ST-078, ST-081: drains every registered worker thread’s speculative
buffer (review fix — this plan’s header “cross-thread visibility”
decision) of records with at <= up_to, merged by (at, registration index) — see write::VcState::drain_committed_changes’s own doc
comment for the full removal/merge algorithm this forwards to.
Waveform/VPI/$display-style consumers are meant to be the only
callers of this method in a real simulation run.
See StateEngine::advance_gvt’s doc comment (ST-124) for the
buffer-growth sizing risk this method’s drain rate must keep bounded.
Sourcepub fn set_change_log(&mut self, on: bool)
pub fn set_change_log(&mut self, on: bool)
Whether value changes are logged for Self::drain_committed_changes
(on by default). A run whose observers read STATE directly and never
drain must turn the log off, or it grows with every change of the run
(ST-124: 70-130 GB on a few minutes of a real design).
Trait Implementations§
Source§impl SignalAccess for StateEngine
impl SignalAccess for StateEngine
fn read(&self, cell: StateCellId) -> FourStateValue
Source§fn write(&mut self, cell: StateCellId, value: FourStateValue) -> bool
fn write(&mut self, cell: StateCellId, value: FourStateValue) -> bool
true iff the write actually changed the stored value (the
old-vs-new compare state.md §3 requires before any VC record is emitted).Source§fn read_row(&self, cell: StateCellId, row: u64) -> FourStateValue
fn read_row(&self, cell: StateCellId, row: u64) -> FourStateValue
SlotClass::Memory). Only implemented by
the real StateEngine; a test double that never stages a memory NBA
never reaches this.Source§fn write_row(
&mut self,
cell: StateCellId,
row: u64,
value: FourStateValue,
) -> bool
fn write_row( &mut self, cell: StateCellId, row: u64, value: FourStateValue, ) -> bool
true iff the row changed.Source§fn take_alias_changes(&mut self) -> Vec<AliasChange>
fn take_alias_changes(&mut self) -> Vec<AliasChange>
state::alias), drained by the scheduler after
each commit so it can notify their waiters. Empty for every engine
that has no window alias.Source§fn read_wide_window(
&self,
cell: StateCellId,
lo_word: usize,
words: usize,
) -> (Vec<u64>, Vec<u64>)
fn read_wide_window( &self, cell: StateCellId, lo_word: usize, words: usize, ) -> (Vec<u64>, Vec<u64>)
[lo_word, lo_word + words) of a wide cell, as
(aval, bval). The default reads the whole value; StateEngine
reads just the window (a nonblocking write to a few bits of a huge
vector must not cost O(N)).Source§fn write_wide_window(
&mut self,
cell: StateCellId,
lo_word: usize,
aval: &[u64],
bval: &[u64],
) -> bool
fn write_wide_window( &mut self, cell: StateCellId, lo_word: usize, aval: &[u64], bval: &[u64], ) -> bool
aval/bval at lo_word.. of a wide cell;
true iff the value changed. The default patches the whole value;
StateEngine stores just the window.