@crowdedkingdoms/ckx-kit 0.0.0-stage → 0.1.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-APACHE +176 -0
- package/LICENSE-MIT +21 -0
- package/README.md +76 -2
- package/checks/compute-toolchain/host-catalog.json +68 -0
- package/crates/ckx-sdk/Cargo.toml +17 -0
- package/crates/ckx-sdk/LICENSE-APACHE +176 -0
- package/crates/ckx-sdk/LICENSE-MIT +21 -0
- package/crates/ckx-sdk/README.md +47 -0
- package/crates/ckx-sdk/src/abi.rs +115 -0
- package/crates/ckx-sdk/src/cron.rs +717 -0
- package/crates/ckx-sdk/src/emit.rs +140 -0
- package/crates/ckx-sdk/src/lib.rs +588 -0
- package/crates/ckx-sdk/src/model.rs +482 -0
- package/crates/ckx-sdk/src/node.rs +942 -0
- package/crates/ckx-sdk/src/rt.rs +519 -0
- package/crates/crowdy-client-sdk/Cargo.toml +15 -0
- package/crates/crowdy-client-sdk/LICENSE-APACHE +176 -0
- package/crates/crowdy-client-sdk/LICENSE-MIT +21 -0
- package/crates/crowdy-client-sdk/README.md +25 -0
- package/crates/crowdy-client-sdk/src/lib.rs +698 -0
- package/dist/checks/src/build/client-capabilities.js +123 -0
- package/dist/checks/src/build/exec-source.js +343 -0
- package/dist/checks/src/build/rust-source-guard.js +165 -0
- package/dist/checks/src/build/wasm-inspect.js +249 -0
- package/dist/cli/src/build.js +99 -0
- package/dist/cli/src/crate.js +64 -0
- package/dist/cli/src/doctor.js +51 -0
- package/dist/cli/src/kit.js +66 -0
- package/dist/cli/src/main.js +109 -0
- package/dist/cli/src/new.js +74 -0
- package/dist/cli/src/tools.js +104 -0
- package/kit.pin +9 -0
- package/package.json +81 -4
- package/rust-toolchain.toml +6 -0
- package/starters/ckx.json +9 -0
- package/starters/grid-mod/Cargo.toml +13 -0
- package/starters/grid-mod/src/lib.rs +82 -0
- package/starters/matchmaker/Cargo.toml +13 -0
- package/starters/matchmaker/src/lib.rs +362 -0
- package/starters/npc-mobs/Cargo.toml +14 -0
- package/starters/npc-mobs/src/lib.rs +469 -0
- package/starters/session/Cargo.toml +13 -0
- package/starters/session/src/lib.rs +395 -0
- package/starters/world-tick/Cargo.toml +14 -0
- package/starters/world-tick/src/lib.rs +346 -0
- package/templates/client/Cargo.toml +16 -0
- package/templates/client/src/lib.rs +31 -0
- package/templates/hub/Cargo.toml +11 -0
- package/templates/hub/src/lib.rs +73 -0
- package/templates/spoke/Cargo.toml +11 -0
- package/templates/spoke/src/lib.rs +53 -0
- package/tools/instrument/Cargo.lock +140 -0
- package/tools/instrument/Cargo.toml +11 -0
- package/tools/instrument/src/main.rs +165 -0
- package/vendor.lock +167 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
//! Realtime events: what players near a chunk, or in a channel, see at once. The host sends
|
|
2
|
+
//! them straight to the realtime tier (the servers players' clients are connected to), not
|
|
3
|
+
//! through the game API, and players' clients receive them as the same messages other players'
|
|
4
|
+
//! actions produce. Use [`Ctx::publish`](crate::Ctx::publish) topics for game state a client
|
|
5
|
+
//! subscribes to through the gateway; use these for the world everyone around sees.
|
|
6
|
+
//!
|
|
7
|
+
//! Limits: a payload is at most [`MAX_PAYLOAD`] bytes, replication distance at most 8 chunks.
|
|
8
|
+
//! Each app has a rate on each host; beyond it events are dropped and counted, not queued.
|
|
9
|
+
|
|
10
|
+
use crate::node::ChunkPos;
|
|
11
|
+
use crate::{CallError, Status, abi};
|
|
12
|
+
|
|
13
|
+
pub const MAX_PAYLOAD: usize = 1024;
|
|
14
|
+
pub const MAX_DISTANCE: u8 = 8;
|
|
15
|
+
|
|
16
|
+
/// A 32-byte actor id, as the realtime tier addresses actors. Players' actors use their
|
|
17
|
+
/// 32-character uuid; a server-driven actor (an NPC, a mob) can use any 32 bytes.
|
|
18
|
+
pub fn actor_id(uuid: &str) -> [u8; 32] {
|
|
19
|
+
let mut out = [0u8; 32];
|
|
20
|
+
let b = uuid.as_bytes();
|
|
21
|
+
out[..b.len().min(32)].copy_from_slice(&b[..b.len().min(32)]);
|
|
22
|
+
out
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// What a spatial event is to the players who receive it.
|
|
26
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
27
|
+
pub enum Spatial<'a> {
|
|
28
|
+
/// An actor's state: a server-driven actor appears and moves like a player's.
|
|
29
|
+
Actor { state: &'a [u8] },
|
|
30
|
+
/// A voxel edit at `(x, y, z)` within the chunk, folded into the live mesh. The durable
|
|
31
|
+
/// write is `ctx.world().set_voxels`, which already shows it; this is for effects.
|
|
32
|
+
Voxel { x: i16, y: i16, z: i16, voxel_type: i16, state: &'a [u8] },
|
|
33
|
+
/// A server event (`event_type`, then `state`), as games route server events.
|
|
34
|
+
Event { event_type: u16, state: &'a [u8] },
|
|
35
|
+
/// An app-defined payload.
|
|
36
|
+
Generic { payload: &'a [u8] },
|
|
37
|
+
/// A message to the one actor named as the event's actor.
|
|
38
|
+
ActorMessage { payload: &'a [u8] },
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl Spatial<'_> {
|
|
42
|
+
fn opcode(&self) -> u8 {
|
|
43
|
+
match self {
|
|
44
|
+
Spatial::Actor { .. } => 130,
|
|
45
|
+
Spatial::Voxel { .. } => 133,
|
|
46
|
+
Spatial::Event { .. } => 139,
|
|
47
|
+
Spatial::Generic { .. } => 140,
|
|
48
|
+
Spatial::ActorMessage { .. } => 142,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fn payload(&self) -> Vec<u8> {
|
|
53
|
+
match *self {
|
|
54
|
+
Spatial::Actor { state } => state.to_vec(),
|
|
55
|
+
Spatial::Voxel { x, y, z, voxel_type, state } => {
|
|
56
|
+
let mut p = Vec::with_capacity(10 + state.len());
|
|
57
|
+
for v in [x, y, z, voxel_type] {
|
|
58
|
+
p.extend_from_slice(&v.to_le_bytes());
|
|
59
|
+
}
|
|
60
|
+
p.extend_from_slice(&(state.len() as u16).to_le_bytes());
|
|
61
|
+
p.extend_from_slice(state);
|
|
62
|
+
p
|
|
63
|
+
}
|
|
64
|
+
Spatial::Event { event_type, state } => {
|
|
65
|
+
let mut p = event_type.to_le_bytes().to_vec();
|
|
66
|
+
p.extend_from_slice(state);
|
|
67
|
+
p
|
|
68
|
+
}
|
|
69
|
+
Spatial::Generic { payload } | Spatial::ActorMessage { payload } => payload.to_vec(),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn refused(message: &str) -> CallError {
|
|
75
|
+
CallError { status: Status::BadRequest, message: message.into() }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
fn send(inject: &[u8]) -> Result<(), CallError> {
|
|
79
|
+
match Status::from_u8(abi::emit(inject) as u8) {
|
|
80
|
+
Status::Ok => Ok(()),
|
|
81
|
+
status => Err(CallError { status, message: "realtime event refused".into() }),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// The inject for a spatial event (the host writes the app in).
|
|
86
|
+
pub(crate) fn spatial(at: ChunkPos, distance: u8, actor: &[u8; 32], event: Spatial<'_>) -> Result<(), CallError> {
|
|
87
|
+
let payload = event.payload();
|
|
88
|
+
if payload.len() > MAX_PAYLOAD {
|
|
89
|
+
return Err(refused("a realtime payload is at most 1024 bytes"));
|
|
90
|
+
}
|
|
91
|
+
if distance > MAX_DISTANCE {
|
|
92
|
+
return Err(refused("replication distance is at most 8 chunks"));
|
|
93
|
+
}
|
|
94
|
+
let mut m = Vec::with_capacity(70 + payload.len());
|
|
95
|
+
m.push(23);
|
|
96
|
+
m.push(event.opcode());
|
|
97
|
+
m.extend_from_slice(&0u64.to_le_bytes());
|
|
98
|
+
for v in [at.x, at.y, at.z] {
|
|
99
|
+
m.extend_from_slice(&v.to_le_bytes());
|
|
100
|
+
}
|
|
101
|
+
m.push(distance);
|
|
102
|
+
m.push(0);
|
|
103
|
+
m.extend_from_slice(actor);
|
|
104
|
+
m.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
|
105
|
+
m.extend_from_slice(&payload);
|
|
106
|
+
send(&m)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// The inject for a channel message.
|
|
110
|
+
pub(crate) fn channel(channel: u64, sender: &[u8; 32], payload: &[u8]) -> Result<(), CallError> {
|
|
111
|
+
if payload.len() > MAX_PAYLOAD {
|
|
112
|
+
return Err(refused("a realtime payload is at most 1024 bytes"));
|
|
113
|
+
}
|
|
114
|
+
let mut m = Vec::with_capacity(51 + payload.len());
|
|
115
|
+
m.push(24);
|
|
116
|
+
m.extend_from_slice(&0u64.to_le_bytes());
|
|
117
|
+
m.extend_from_slice(&channel.to_le_bytes());
|
|
118
|
+
m.extend_from_slice(sender);
|
|
119
|
+
m.extend_from_slice(&(payload.len() as u16).to_le_bytes());
|
|
120
|
+
m.extend_from_slice(payload);
|
|
121
|
+
send(&m)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#[cfg(test)]
|
|
125
|
+
mod tests {
|
|
126
|
+
use super::*;
|
|
127
|
+
|
|
128
|
+
#[test]
|
|
129
|
+
fn payloads_are_framed_as_clients_decode_them() {
|
|
130
|
+
let v = Spatial::Voxel { x: 1, y: -2, z: 3, voxel_type: 54, state: &[9] };
|
|
131
|
+
assert_eq!(v.payload(), vec![1, 0, 0xfe, 0xff, 3, 0, 54, 0, 1, 0, 9]);
|
|
132
|
+
let v = Spatial::Voxel { x: 0, y: 0, z: 0, voxel_type: -2, state: &[] };
|
|
133
|
+
assert_eq!(v.payload(), vec![0, 0, 0, 0, 0, 0, 0xfe, 0xff, 0, 0], "an app's voxel type is signed");
|
|
134
|
+
assert_eq!(Spatial::Event { event_type: 7, state: b"x" }.payload(), vec![7, 0, b'x']);
|
|
135
|
+
assert_eq!(actor_id("ab")[..3], [b'a', b'b', 0]);
|
|
136
|
+
assert!(spatial(ChunkPos::new(0, 0, 0), 9, &[0; 32], Spatial::Generic { payload: b"" }).is_err());
|
|
137
|
+
assert!(channel(1, &[0; 32], &[0; MAX_PAYLOAD + 1]).is_err());
|
|
138
|
+
assert!(spatial(ChunkPos::new(0, 0, 0), 2, &[0; 32], Spatial::Actor { state: b"pose" }).is_ok());
|
|
139
|
+
}
|
|
140
|
+
}
|