Skip to main content

interstice_cli/
example.rs

1use crate::{
2    data_directory::nodes_dir,
3    node_registry::{NodeRecord, NodeRegistry},
4    node_utils,
5};
6use interstice_core::{IntersticeError, Node};
7
8const HELLO_BYTES: &[u8] = include_bytes!(concat!(
9    env!("CARGO_MANIFEST_DIR"),
10    "/module_examples/hello_example.wasm"
11));
12const CALLER_BYTES: &[u8] = include_bytes!(concat!(
13    env!("CARGO_MANIFEST_DIR"),
14    "/module_examples/caller_example.wasm"
15));
16const GRAPHICS_BYTES: &[u8] = include_bytes!(concat!(
17    env!("CARGO_MANIFEST_DIR"),
18    "/module_examples/graphics_example.wasm"
19));
20const AUDIO_BYTES: &[u8] = include_bytes!(concat!(
21    env!("CARGO_MANIFEST_DIR"),
22    "/module_examples/audio_example.wasm"
23));
24const AGAR_SERVER_BYTES: &[u8] = include_bytes!(concat!(
25    env!("CARGO_MANIFEST_DIR"),
26    "/module_examples/agar_server.wasm"
27));
28const AGAR_CLIENT_BYTES: &[u8] = include_bytes!(concat!(
29    env!("CARGO_MANIFEST_DIR"),
30    "/module_examples/agar_client.wasm"
31));
32const BENCHMARK_WORKLOAD_BYTES: &[u8] = include_bytes!(concat!(
33    env!("CARGO_MANIFEST_DIR"),
34    "/module_examples/benchmark_workload.wasm"
35));
36const DEFAULT_GRAPHICS_BYTES: &[u8] = include_bytes!(concat!(
37    env!("CARGO_MANIFEST_DIR"),
38    "/module_defaults/graphics.wasm"
39));
40const DEFAULT_INPUT_BYTES: &[u8] = include_bytes!(concat!(
41    env!("CARGO_MANIFEST_DIR"),
42    "/module_defaults/input.wasm"
43));
44
45struct ExampleModule {
46    bytes: &'static [u8],
47}
48
49struct ExampleConfig {
50    name: &'static str,
51    port: u32,
52    modules: Vec<ExampleModule>,
53}
54
55fn example_config(example_name: &str) -> Result<ExampleConfig, IntersticeError> {
56    match example_name {
57        "hello" => Ok(ExampleConfig {
58            name: "hello-example",
59            port: 8080,
60            modules: vec![ExampleModule { bytes: HELLO_BYTES }],
61        }),
62        "caller" => Ok(ExampleConfig {
63            name: "caller-example",
64            port: 8081,
65            modules: vec![ExampleModule {
66                bytes: CALLER_BYTES,
67            }],
68        }),
69        "graphics" => Ok(ExampleConfig {
70            name: "graphics-example",
71            port: 8082,
72            modules: vec![ExampleModule {
73                bytes: GRAPHICS_BYTES,
74            }],
75        }),
76        "audio" => Ok(ExampleConfig {
77            name: "audio-example",
78            port: 8083,
79            modules: vec![ExampleModule { bytes: AUDIO_BYTES }],
80        }),
81        "agar-server" => Ok(ExampleConfig {
82            name: "agar-server-example",
83            port: 8080,
84            modules: vec![ExampleModule {
85                bytes: AGAR_SERVER_BYTES,
86            }],
87        }),
88        "agar-client" => Ok(ExampleConfig {
89            name: "agar-client-example",
90            port: 8084,
91            modules: vec![
92                ExampleModule {
93                    bytes: DEFAULT_INPUT_BYTES,
94                },
95                ExampleModule {
96                    bytes: DEFAULT_GRAPHICS_BYTES,
97                },
98                ExampleModule {
99                    bytes: AGAR_CLIENT_BYTES,
100                },
101            ],
102        }),
103        "benchmark" => Ok(ExampleConfig {
104            name: "benchmark-example",
105            port: 8085,
106            modules: vec![ExampleModule {
107                bytes: BENCHMARK_WORKLOAD_BYTES,
108            }],
109        }),
110        _ => Err(IntersticeError::Internal(format!(
111            "Unknown example '{example_name}'. Expected hello, caller, graphics, audio, agar-server, agar-client, or benchmark."
112        ))),
113    }
114}
115
116pub async fn example(example_name: &str) -> Result<(), IntersticeError> {
117    let config = example_config(example_name)?;
118    let mut registry = NodeRegistry::load()?;
119    let name = config.name.to_string();
120
121    // Remove existing node if present to ensure clean state
122    if registry.get(&name).is_some() {
123        node_utils::remove_node_with_data(&mut registry, &name)?;
124    }
125
126    // Create new node
127    let node = Node::new(&nodes_dir(), config.port)?;
128    registry.add(NodeRecord {
129        name,
130        address: format!("127.0.0.1:{}", config.port),
131        node_id: Some(node.id.to_string()),
132        local: true,
133        last_seen: None,
134    })?;
135
136    // Write module bytes directly to disk (node.start() will load them)
137    let modules_path = nodes_dir().join(node.id.to_string()).join("modules");
138    for (idx, module_config) in config.modules.iter().enumerate() {
139        // Use a simple directory name (actual module name will be read from WASM)
140        let module_dir = modules_path.join(format!("module_{}", idx));
141        std::fs::create_dir_all(&module_dir).map_err(|err| {
142            IntersticeError::Internal(format!("Failed to create module directory: {err}"))
143        })?;
144        std::fs::write(module_dir.join("module.wasm"), module_config.bytes).map_err(|err| {
145            IntersticeError::Internal(format!("Failed to write module WASM: {err}"))
146        })?;
147    }
148
149    // Now start the node, which will load modules from disk
150    node.start().await?;
151    Ok(())
152}