///|
/// A value that can be stored in a runtime-owned view-state slot.
///
/// The type key is part of slot identity, so two slots with the same logical
/// key but different value types cannot alias. Implementations must use a
/// stable, versioned type key and an exact, deterministic byte encoding.
pub(open) trait ViewStateValue {
fn view_state_type_key() -> DeclarationKey
fn encode_view_state(Self) -> Bytes
fn decode_view_state(Self, Bytes) -> Self?
}
///|
/// A typed descriptor for one node-scoped transient state value.
///
/// Descriptors are immutable declaration-time values. The stored bytes live in
/// the reconciled runtime element and are preserved only while that element's
/// identity is reused.
pub struct ViewStateSlot[T] {
priv key : DeclarationKey
priv initial : T
}
///|
/// Runtime-owned storage behind `ViewStateContext`.
pub struct ViewStateSlots {
priv values : Map[DeclarationKey, Bytes]
} derive(Eq, Debug)
///|
pub fn[T : ViewStateValue] ViewStateSlot::new(
key~ : DeclarationKey,
initial~ : T,
) -> ViewStateSlot[T] {
{
key: DeclarationKey::record(type_tag="moui.core.ViewStateSlot.v1", fields=[
("type", T::view_state_type_key()),
("slot", key),
]),
initial,
}
}
///|
pub fn ViewStateSlots::empty() -> ViewStateSlots {
{ values: Map([]) }
}
///|
/// Read a typed slot, returning the descriptor's initial value when this
/// element has not written the slot yet.
pub fn[T : ViewStateValue] ViewStateContext::read_slot(
self : ViewStateContext,
slot : ViewStateSlot[T],
) -> T {
match self.slots.values.get(slot.key) {
Some(bytes) => slot.initial.decode_view_state(bytes).unwrap_or(slot.initial)
None => slot.initial
}
}
///|
/// Return a state proposal with one typed slot updated. The current context is
/// never mutated, so runtimes can validate an event or semantic action before
/// committing the returned state.
pub fn[T : ViewStateValue] ViewStateContext::with_slot(
self : ViewStateContext,
slot : ViewStateSlot[T],
value : T,
) -> ViewStateContext {
let encoded = value.encode_view_state()
match self.slots.values.get(slot.key) {
Some(current) if current == encoded => self
_ => {
let values = self.slots.values.copy()
values[slot.key] = encoded
{ ..self, slots: { values, } }
}
}
}
///|
/// Return a state proposal with one slot reset to its initial value.
pub fn[T] ViewStateContext::without_slot(
self : ViewStateContext,
slot : ViewStateSlot[T],
) -> ViewStateContext {
if !self.slots.values.contains(slot.key) {
self
} else {
let values = self.slots.values.copy()
values.remove(slot.key)
{ ..self, slots: { values, } }
}
}
///|
pub impl ViewStateValue for Bool with fn view_state_type_key() {
DeclarationKey::string("moonbit.Bool")
}
///|
pub impl ViewStateValue for Bool with fn encode_view_state(self) {
if self {
b"\x01"
} else {
b"\x00"
}
}
///|
pub impl ViewStateValue for Bool with fn decode_view_state(_self, bytes) {
match bytes {
b"\x00" => Some(false)
b"\x01" => Some(true)
_ => None
}
}
///|
pub impl ViewStateValue for Int with fn view_state_type_key() {
DeclarationKey::string("moonbit.Int32")
}
///|
pub impl ViewStateValue for Int with fn encode_view_state(self) {
let output = Buffer(size_hint=4)
output.write_int_be(self)
output.to_bytes()
}
///|
pub impl ViewStateValue for Int with fn decode_view_state(_self, bytes) {
if bytes.length() != 4 {
None
} else {
let bits = (bytes[0].to_uint() << 24) |
(bytes[1].to_uint() << 16) |
(bytes[2].to_uint() << 8) |
bytes[3].to_uint()
Some(bits.reinterpret_as_int())
}
}
///|
pub impl ViewStateValue for Double with fn view_state_type_key() {
DeclarationKey::string("moonbit.Double64")
}
///|
pub impl ViewStateValue for Double with fn encode_view_state(self) {
self.reinterpret_as_uint64().to_be_bytes()
}
///|
pub impl ViewStateValue for Double with fn decode_view_state(_self, bytes) {
if bytes.length() != 8 {
None
} else {
let mut bits = 0UL
for byte in bytes {
bits = (bits << 8) | byte.to_uint64()
}
Some(bits.reinterpret_as_double())
}
}
///|
pub impl ViewStateValue for String with fn view_state_type_key() {
DeclarationKey::string("moonbit.String.utf8")
}
///|
pub impl ViewStateValue for String with fn encode_view_state(self) {
@utf8.encode(self[:], bom=false)
}
///|
pub impl ViewStateValue for String with fn decode_view_state(_self, bytes) {
Some(@utf8.decode(bytes[:], ignore_bom=false)) catch {
_ => None
}
}
///|
pub impl ViewStateValue for Bytes with fn view_state_type_key() {
DeclarationKey::string("moonbit.Bytes")
}
///|
pub impl ViewStateValue for Bytes with fn encode_view_state(self) {
self
}
///|
pub impl ViewStateValue for Bytes with fn decode_view_state(_self, bytes) {
Some(bytes)
}