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
Binary file modified pkgs/id/examples/roc-counter/counter.wasm
100755 → 100644
Binary file not shown.
27 changes: 22 additions & 5 deletions pkgs/id/examples/roc-counter/main.roc
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
app [program] { pf: platform "../roc-world/platform.roc" }

import pf.Screen
import pf.RocText
Comment thread
dezren39 marked this conversation as resolved.
Resolved
Comment on lines +3 to +4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Grant the new imports in source-compilation integration tests

When Roc and the platform assets are available, test_worlds_compile_roc_sources_on_the_fly and test_worlds_compile_native_workers_on_the_fly in tests/cli_integration.rs now fail before compilation. Both load this source but start fresh servers without capability grants; WorldCore::new initializes an empty grant set, and WorldService::compile_and_install passes its effective grants to check_imports, which rejects Screen. Grant Screen and RocText in these fixtures before compiling; updating the direct compilation unit tests does not cover these service-level consumers.

Useful? React with 👍 / 👎.


Model := { count : I64 }

program = { init, update, view, layout, records, wants, snapshot, restore }

init : U64 -> Model
init = |_seed| { count: 0 }

## Events are matched after a RocText trim, so a journaled or typed
## " inc " counts the same as "inc".
update : Model, U64, Str -> Model
update = |model, _pid, ev| if ev == "inc" { count: model.count + 1 } else model
update = |model, _pid, ev| if RocText.trim(ev) == "inc" { count: model.count + 1 } else model

## The counter renders through Screen, like roc-screen: a boxed 20x3
## screen whose JSON the host draws, instead of a bare text view.
view : Model -> Str
view = |model| Str.concat("count=", model.count.to_str())
view = |model| render(model, 20, 3)
Comment thread
dezren39 marked this conversation as resolved.
Resolved

## Structured data the host stores and replicates: a JSON object of records.
## This program renders the same layout at any viewport.
# Reflow: a wide viewport gets a wider box, a short one keeps three rows.
layout : Model, U64, U64 -> Str
layout = |model, _width, _height| view(model)
layout = |model, width, height| {
w = if width >= 30 30 else 20
h = if height >= 4 3 else 3
render(model, w, h)
}

render : Model, U64, U64 -> Str
render = |model, w, h| {
base = Screen.boxed(Screen.new(w, h), 0, 0, w, h, "counter")
Screen.render_json(Screen.put_text(base, 2, 1, "count=${model.count.to_str()}", { fg: 0, bg: 0, bold: Bool.False }))
Comment on lines +33 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rebaseline the counter's rendering fuel budgets

The sandbox-enabled suite still enforces 100,000-fuel budgets for both counter View and Layout in src/sandbox.rs::FUEL_BUDGETS, despite replacing the inexpensive text output with Screen construction and JSON rendering. The equivalent existing roc-screen renderer has recorded baselines of 123,675 fuel for its 20×3 view and 378,384 for its wide layout, already above those counter limits. Consequently checked_in_examples_stay_within_fuel_budgets retains obsolete thresholds for the rebuilt artifact. Measure the new counter and deliberately update its rendering budgets alongside this change.

Useful? React with 👍 / 👎.

}

## Structured data the host stores and replicates: a JSON object of records.
records : Model -> Str
records = |model| "{\"count\":${model.count.to_str()}}"

Expand Down
15 changes: 14 additions & 1 deletion pkgs/id/examples/tic-tac-toe/main.roc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
app [program] { pf: platform "../roc-world/platform.roc" }

import pf.RocJson

# A two-player tic-tac-toe world.
#
# State is a 9-character board string, the ply count, and the winner. The
Expand Down Expand Up @@ -59,7 +61,13 @@ layout : Model, U64, U64 -> Str
layout = |model, _width, _height| view(model)

records : Model -> Str
records = |model| "{\"board\":\"${model.board}\",\"plays\":${model.plays.to_str()},\"winner\":\"${model.winner}\"}"
records = |model| encode_or_crash(
RocJson.Object([
{ key: "board", value: RocJson.String(model.board) },
{ key: "plays", value: RocJson.Number(model.plays.to_str()) },
{ key: "winner", value: RocJson.String(model.winner) },
]),
)

## The board is the whole state: the ply count is the number of marks on it
## and the winner follows from its lines, so nothing else is stored.
Expand All @@ -79,6 +87,11 @@ restore = |board| {

# --- helpers ---------------------------------------------------------------

encode_or_crash = |value| match RocJson.encode(value) {
Ok(text) => text
Err(_) => crash "tic-tac-toe attempted to encode an invalid or oversized JSON value"
}

empty_cell : U8
empty_cell = 45 # '-'

Expand Down
Binary file modified pkgs/id/examples/tic-tac-toe/tic-tac-toe.wasm
100755 → 100644
Binary file not shown.
77 changes: 58 additions & 19 deletions pkgs/id/src/sandbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -951,11 +951,23 @@ mod tests {
let wasm = include_bytes!("../examples/roc-counter/counter.wasm");
let runner = Sandbox::compile(wasm, SandboxLimits::default()).unwrap();
let mut world = runner.instantiate(7).unwrap();
assert_eq!(world.view().unwrap(), b"count=0");
assert!(
String::from_utf8(world.view().unwrap())
.unwrap()
.contains("count=0")
);
world.update(1, b"inc").unwrap();
assert_eq!(world.view().unwrap(), b"count=1");
assert!(
String::from_utf8(world.view().unwrap())
.unwrap()
.contains("count=1")
);
world.update(1, b"inc").unwrap();
assert_eq!(world.view().unwrap(), b"count=2");
assert!(
String::from_utf8(world.view().unwrap())
.unwrap()
.contains("count=2")
);
}

#[tokio::test]
Expand All @@ -971,25 +983,37 @@ mod tests {
);
let (_, capability) = actor.issue("Ada", WorldScopes::GUEST).await.unwrap();

assert_eq!(
actor.view(capability.clone()).await.unwrap().as_deref(),
Some("count=0")
assert!(
actor
.view(capability.clone())
.await
.unwrap()
.as_deref()
.is_some_and(|view| view.contains("count=0"))
);
actor
.input(capability.clone(), b"inc".to_vec())
.await
.unwrap();
assert_eq!(
actor.view(capability.clone()).await.unwrap().as_deref(),
Some("count=1")
assert!(
actor
.view(capability.clone())
.await
.unwrap()
.as_deref()
.is_some_and(|view| view.contains("count=1"))
);
actor
.chat(capability.clone(), "chat does not mutate the game")
.await
.unwrap();
assert_eq!(
actor.view(capability).await.unwrap().as_deref(),
Some("count=1")
assert!(
actor
.view(capability)
.await
.unwrap()
.as_deref()
.is_some_and(|view| view.contains("count=1"))
);
}

Expand All @@ -1003,7 +1027,11 @@ mod tests {
world.update(1, b"inc").unwrap();
assert_eq!(world.records().unwrap().unwrap(), br#"{"count":2}"#);
// A view between records calls must not disturb either output.
assert_eq!(world.view().unwrap(), b"count=2");
assert!(
String::from_utf8(world.view().unwrap())
.unwrap()
.contains("count=2")
);
assert_eq!(world.records().unwrap().unwrap(), br#"{"count":2}"#);
}

Expand Down Expand Up @@ -1065,15 +1093,27 @@ mod tests {
assert_eq!(live.snapshot().unwrap().unwrap(), b"3");
let mut restored = runner.instantiate(7).unwrap();
restored.restore(b"3").unwrap();
assert_eq!(restored.view().unwrap(), b"count=3");
assert!(
String::from_utf8(restored.view().unwrap())
.unwrap()
.contains("count=3")
);
restored.update(1, b"inc").unwrap();
assert_eq!(restored.view().unwrap(), b"count=4");
assert!(
String::from_utf8(restored.view().unwrap())
.unwrap()
.contains("count=4")
);
// The trait-level snapshot is the verified one the actor journals.
assert_eq!(live.snapshot_via_trait(), Some("3".to_owned()));
// Garbage restores to a defined state instead of trapping.
let mut garbage = runner.instantiate(7).unwrap();
garbage.restore(b"not a number").unwrap();
assert_eq!(garbage.view().unwrap(), b"count=0");
assert!(
String::from_utf8(garbage.view().unwrap())
.unwrap()
.contains("count=0")
);

// Tic-tac-toe: the board alone carries the ply count and the winner.
let wasm = include_bytes!("../examples/tic-tac-toe/tic-tac-toe.wasm");
Expand Down Expand Up @@ -1465,9 +1505,8 @@ mod tests {
)
.unwrap();
let _ = crate::world::WorldProgram::records(&mut program).unwrap();
assert_eq!(
view(&mut program),
"count=1",
assert!(
view(&mut program).contains("count=1"),
"wasm handles the actor order"
);
}
Expand Down
12 changes: 8 additions & 4 deletions pkgs/id/src/world_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,7 @@ restore = |text| match U64.from_str(text) {
native: false,
},
&compiler,
&[],
&["Screen".to_owned(), "RocText".to_owned()],
COMPILE_TIMEOUT,
)
.await
Expand Down Expand Up @@ -1115,7 +1115,11 @@ restore = |text| match U64.from_str(text) {
},
)
.unwrap();
assert_eq!(world.view().unwrap(), b"count=1");
assert!(
String::from_utf8(world.view().unwrap())
.unwrap()
.contains("count=1")
);
}

#[tokio::test]
Expand Down Expand Up @@ -1212,7 +1216,7 @@ restore = |text| match U64.from_str(text) {
native: true,
},
&compiler,
&[],
&["Screen".to_owned(), "RocText".to_owned()],
COMPILE_TIMEOUT,
)
.await
Expand Down Expand Up @@ -1251,7 +1255,7 @@ restore = |text| match U64.from_str(text) {
},
)
.unwrap();
assert_eq!(view.as_deref(), Some("count=1"));
assert!(view.as_deref().is_some_and(|view| view.contains("count=1")));
}

#[test]
Expand Down
12 changes: 10 additions & 2 deletions pkgs/id/src/world_net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1484,7 +1484,11 @@ mod tests {
assert_eq!(next(&mut client).await.unwrap()["type"], "snapshot");
let initial = next(&mut client).await.unwrap();
assert_eq!(initial["type"], "view");
assert_eq!(initial["data"], "count=0");
assert!(
initial["data"]
.as_str()
.is_some_and(|data| data.contains("count=0"))
);

client
.send_json(&serde_json::json!({ "type": "input", "data_hex": "696e63" }))
Expand All @@ -1493,7 +1497,11 @@ mod tests {
let event = next(&mut client).await.unwrap();
assert_eq!(event["type"], "event");
let view = next(&mut client).await.unwrap();
assert_eq!(view["data"], "count=1");
assert!(
view["data"]
.as_str()
.is_some_and(|data| data.contains("count=1"))
);

client_ep.close().await;
router.shutdown().await.unwrap();
Expand Down
48 changes: 37 additions & 11 deletions pkgs/id/src/world_session/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1553,12 +1553,23 @@ mod tests {
blobs.blobs().has(hash).await.unwrap(),
"uploaded module is pinned in the blob store"
);
assert_eq!(
world.view(guest.clone()).await.unwrap().as_deref(),
Some("count=0")
assert!(
world
.view(guest.clone())
.await
.unwrap()
.as_deref()
.is_some_and(|view| view.contains("count=0"))
);
world.input(guest.clone(), b"inc".to_vec()).await.unwrap();
assert_eq!(world.view(guest).await.unwrap().as_deref(), Some("count=1"));
assert!(
world
.view(guest)
.await
.unwrap()
.as_deref()
.is_some_and(|view| view.contains("count=1"))
);
}

#[cfg(feature = "sandbox")]
Expand Down Expand Up @@ -1685,9 +1696,13 @@ mod tests {
let service =
WorldService::new(world.clone(), Some("admin".to_owned())).with_blob_store(blobs);
let (_, guest) = world.issue("guest", WorldScopes::GUEST).await.unwrap();
assert_eq!(
world.view(guest.clone()).await.unwrap().as_deref(),
Some("count=0")
assert!(
world
.view(guest.clone())
.await
.unwrap()
.as_deref()
.is_some_and(|view| view.contains("count=0"))
);

let bad = b"not wasm".to_vec();
Expand All @@ -1699,12 +1714,23 @@ mod tests {
.unwrap_err(),
InstallError::InvalidModule
);
assert_eq!(
world.view(guest.clone()).await.unwrap().as_deref(),
Some("count=0")
assert!(
world
.view(guest.clone())
.await
.unwrap()
.as_deref()
.is_some_and(|view| view.contains("count=0"))
);
world.input(guest.clone(), b"inc".to_vec()).await.unwrap();
assert_eq!(world.view(guest).await.unwrap().as_deref(), Some("count=1"));
assert!(
world
.view(guest)
.await
.unwrap()
.as_deref()
.is_some_and(|view| view.contains("count=1"))
);
}

#[tokio::test]
Expand Down
Loading