1use std::collections::{BTreeMap, HashMap};
6use std::fs;
7use std::path::PathBuf;
8
9use brdb::fs::BrFs;
10use brdb::schema::WireVariant;
11use brdb::{
12 assets, schemas, AsBrdbValue, BrFsReader, Brdb, Brick, BrickSize, BrickType, Brz,
13 Collision, Direction, Entity, Guid, IntoReader, Owner, Rotation, SavedBrickColor, World,
14};
15
16const ALICE_UUID: &str = "a1b2c3d4-e5f6-4789-8abc-def012345678";
17const BOB_UUID: &str = "00112233-4455-6677-8899-aabbccddeeff";
18
19fn brick_world() -> World {
20 let mut world = World::new();
22 world.meta.bundle.description = "Example World".to_string();
23 world.bricks.push(Brick {
24 position: (0, 0, 6).into(),
25 color: (255, 0, 0).into(),
26 ..Default::default()
27 });
28 world
29}
30
31fn features_world() -> World {
32 let mut world = World::new();
36 world.meta.bundle.description = "Feature fixture".to_string();
37
38 let alice = Guid::from_uuid(uuid::Uuid::parse_str(ALICE_UUID).unwrap());
39 let bob = Guid::from_uuid(uuid::Uuid::parse_str(BOB_UUID).unwrap());
40 world.owners.insert(alice, Owner {
41 user_id: alice,
42 user_name: "alice".to_string(),
43 display_name: "Alice".to_string(),
44 });
45 world.owners.insert(bob, Owner {
46 user_id: bob,
47 user_name: "bob".to_string(),
48 display_name: "Bob".to_string(),
49 });
50
51 let tile = |size: (u16, u16, u16)| BrickType::Procedural {
52 asset: assets::bricks::PB_DEFAULT_TILE, size: BrickSize { x: size.0, y: size.1, z: size.2 },
54 };
55
56 world.bricks.push(Brick {
58 position: (0, 0, 6).into(),
59 color: (255, 0, 0).into(),
60 owner_index: Some(1),
61 ..Default::default()
62 });
63 world.bricks.push(Brick {
65 asset: tile((10, 10, 2)),
66 position: (20, 0, 2).into(),
67 color: (0, 255, 0).into(),
68 owner_index: Some(1),
69 material: assets::materials::METALLIC, material_intensity: 7,
71 direction: Direction::XPositive,
72 rotation: Rotation::Deg90,
73 ..Default::default()
74 });
75 world.bricks.push(Brick {
77 asset: tile((10, 10, 2)),
78 position: (40, 0, 2).into(),
79 color: (0, 0, 255).into(),
80 owner_index: Some(2),
81 ..Default::default()
82 });
83 world.bricks.push(Brick {
85 asset: tile((20, 20, 2)),
86 position: (80, 0, 2).into(),
87 color: (255, 255, 0).into(),
88 owner_index: Some(2),
89 ..Default::default()
90 });
91 world.bricks.push(Brick {
93 position: (100, 0, 6).into(),
94 color: (255, 255, 255).into(),
95 owner_index: Some(1),
96 ..Default::default()
97 });
98 world.bricks.push(Brick {
100 asset: assets::bricks::B_2X2_OVERHANG, position: (200, 0, 10).into(),
102 color: (128, 64, 32).into(),
103 material: assets::materials::GLOW, material_intensity: 3,
105 direction: Direction::YNegative,
106 rotation: Rotation::Deg180,
107 collision: Collision { player: false, ..Default::default() },
108 ..Default::default()
109 });
110 world.bricks.push(Brick {
112 asset: BrickType::Procedural {
113 asset: assets::bricks::PB_DEFAULT_BRICK,
114 size: BrickSize { x: 2, y: 2, z: 2 },
115 },
116 position: (300, 0, 2).into(),
117 color: (10, 20, 30).into(),
118 owner_index: Some(1),
119 visible: false,
120 collision: Collision {
121 player: false,
122 weapon: false,
123 interact: false,
124 physics: false,
125 ..Default::default()
126 },
127 ..Default::default()
128 });
129 world
130}
131
132fn chunks_world() -> World {
133 let mut world = World::new();
139 world.meta.bundle.description = "Chunk fixture".to_string();
140 for (pos, color) in [
141 ((0, 0, 0), (1, 2, 3)),
142 ((-1, -1, -1), (4, 5, 6)),
143 ((2048, 0, 0), (1, 2, 3)), ((-2048, 4096, 10), (10, 11, 12)),
145 ((500, 500, 500), (42, 42, 42)),
146 ((2548, 500, 500), (42, 42, 42)), ] {
148 world.bricks.push(Brick {
149 position: pos.into(),
150 color: color.into(),
151 ..Default::default()
152 });
153 }
154 world
155}
156
157fn wires_world() -> World {
158 let mut world = World::new();
162 world.register_all_components();
163 world.meta.bundle.description = "Wire fixture".to_string();
164
165 let (a, a_id) = Brick {
166 position: (30, 0, 1).into(),
167 color: (255, 0, 0).into(),
168 asset: assets::bricks::B_REROUTE,
169 ..Default::default()
170 }
171 .with_component(assets::components::Rerouter)
172 .with_id_split();
173 let (b, b_id) = Brick {
174 position: (15, 0, 1).into(),
175 color: (0, 255, 0).into(),
176 asset: assets::components::LogicGate::BoolAnd.brick(),
177 ..Default::default()
178 }
179 .with_component(assets::components::LogicGate::BoolAnd.component())
180 .with_id_split();
181 let (c, c_id) = Brick {
182 position: (0, 0, 1).into(),
183 color: (0, 0, 255).into(),
184 asset: assets::components::LogicGate::BoolNot.brick(),
185 ..Default::default()
186 }
187 .with_component(assets::components::LogicGate::BoolNot.component())
188 .with_id_split();
189
190 world.add_bricks([a, b, c]);
191 world.add_wire_connection(
193 assets::components::LogicGate::BoolNot.output_of(c_id),
194 assets::components::LogicGate::BoolAnd.input_a_of(b_id),
195 );
196 world.add_wire_connection(
198 assets::components::LogicGate::BoolAnd.output_of(b_id),
199 assets::components::Rerouter::input_of(a_id),
200 );
201
202 world
203}
204
205fn components_world() -> World {
206 let mut world = World::new();
213 world.register_all_components();
214 world.meta.bundle.description = "Component fixture".to_string();
215
216 world.bricks.push(
219 Brick {
220 position: (0, 0, 6).into(),
221 color: (255, 255, 255).into(),
222 ..Default::default()
223 }
224 .with_component(assets::LiteralComponent::new("Component_PointLight").with_data([
225 ("bMatchBrickShape", Box::new(false) as Box<dyn AsBrdbValue>),
226 ("bEnabled", Box::new(true)),
227 ("Brightness", Box::new(500.0f32)),
228 ("Radius", Box::new(800.0f32)),
229 (
230 "Color",
231 Box::new(SavedBrickColor { r: 10, g: 20, b: 30, a: 255 }),
232 ),
233 ("bUseBrickColor", Box::new(false)),
234 ("bCastShadows", Box::new(true)),
235 ])),
236 );
237
238 world.bricks.push(
240 Brick {
241 position: (20, 0, 6).into(),
242 color: (255, 255, 0).into(),
243 ..Default::default()
244 }
245 .with_component(assets::LiteralComponent::new("Component_SpotLight").with_data([
246 ("InnerConeAngle", Box::new(15.0f32) as Box<dyn AsBrdbValue>),
247 ("OuterConeAngle", Box::new(45.0f32)),
248 ("bEnabled", Box::new(true)),
249 ("Brightness", Box::new(300.0f32)),
250 ("Radius", Box::new(600.0f32)),
251 (
252 "Color",
253 Box::new(SavedBrickColor { r: 255, g: 0, b: 0, a: 255 }),
254 ),
255 ("bUseBrickColor", Box::new(false)),
256 ("bCastShadows", Box::new(true)),
257 ])),
258 );
259
260 world.bricks.push(
262 Brick {
263 position: (40, 0, 6).into(),
264 color: (0, 255, 255).into(),
265 ..Default::default()
266 }
267 .with_component(assets::LiteralComponent::new("Component_Interact").with_data([
268 (
269 "Message",
270 Box::new("You interacted!".to_string()) as Box<dyn AsBrdbValue>,
271 ),
272 ("ConsoleTag", Box::new("fixture_interact".to_string())),
273 ("bAllowNearbyInteraction", Box::new(false)),
274 ("bHiddenInteraction", Box::new(true)),
275 ("PromptCustomLabel", Box::new("Open Door".to_string())),
276 ])),
277 );
278
279 world.bricks.push(
282 Brick {
283 position: (60, 0, 1).into(),
284 color: (128, 0, 128).into(),
285 asset: assets::components::BufferTicks::default().brick(),
286 ..Default::default()
287 }
288 .with_component(assets::components::BufferTicks {
289 current_ticks: 3,
290 ticks_to_wait: 10,
291 input: WireVariant::Number(2.5),
292 output: WireVariant::Bool(true),
293 }),
294 );
295
296 world.bricks.push(
300 Brick {
301 position: (80, 0, 1).into(),
302 color: (0, 128, 0).into(),
303 asset: assets::components::LogicGate::Blend.brick(),
304 ..Default::default()
305 }
306 .with_component(assets::components::LogicGate::Blend.component_with_overrides(
307 HashMap::from([
308 (
309 "InputA".into(),
310 Box::new(WireVariant::Number(10.0)) as Box<dyn AsBrdbValue>,
311 ),
312 ("InputB".into(), Box::new(WireVariant::Int(7))),
313 ("Blend".into(), Box::new(0.75f64)),
314 ]),
315 )),
316 );
317
318 world
319}
320
321fn entities_world() -> World {
322 let mut world = World::new();
335 world.register_all_components();
336 world.meta.bundle.description = "Entity fixture".to_string();
337
338 world.bricks.push(Brick {
339 position: (0, 0, 6).into(),
340 color: (200, 50, 50).into(),
341 ..Default::default()
342 });
343 world.bricks.push(Brick {
344 position: (20, 0, 6).into(),
345 color: (50, 200, 50).into(),
346 ..Default::default()
347 });
348
349 world.add_brick_grid(
350 Entity {
351 frozen: true,
352 location: (0.0, 0.0, 40.0).into(),
353 ..Default::default()
354 },
355 [Brick {
356 position: (0, 0, 3).into(),
357 color: (0, 255, 0).into(),
358 ..Default::default()
359 }],
360 );
361
362 world
363}
364
365fn spawner_world() -> World {
366 let mut inner = World::new();
372 inner.meta.bundle.description = "Inner prefab".to_string();
373 inner.bricks.push(Brick {
374 position: (0, 0, 6).into(),
375 color: (255, 0, 0).into(),
376 ..Default::default()
377 });
378 inner.make_prefab();
379 let inner_bytes = {
380 let pending = inner.to_unsaved().unwrap().to_pending().unwrap();
381 let mut buf = Vec::new();
382 pending
383 .to_brz_data(None)
384 .unwrap()
385 .write(&mut buf, None)
386 .unwrap();
387 buf
388 };
389
390 let mut world = World::new();
391 world.register_all_components();
392 world.meta.bundle.description = "Spawner fixture".to_string();
393 let prefab_path = world.add_prefab(inner_bytes);
394 world.bricks.push(
395 Brick {
396 asset: brdb::BrickType::str("B_1x1_Gate_Exec_PrefabSpawner"),
397 position: (0, 0, 1).into(),
398 ..Default::default()
399 }
400 .with_component(
401 assets::LiteralComponent::new("BrickComponentType_WireGraph_Exec_PrefabSpawner")
402 .with_data([(
403 "Prefab",
404 Box::new(prefab_path) as Box<dyn AsBrdbValue>,
405 )]),
406 ),
407 );
408 world.make_prefab();
409 world
410}
411
412fn hash_archive(path: &PathBuf) -> Result<BTreeMap<String, serde_json::Value>, Box<dyn std::error::Error>> {
413 fn collect(fs: &BrFs, prefix: &str, out: &mut Vec<(String, Option<i64>)>) {
414 match fs {
415 BrFs::Root(children) => for (n, c) in children { collect(c, n, out); },
416 BrFs::Folder(_, children) => for (n, c) in children {
417 collect(c, &format!("{prefix}/{n}"), out);
418 },
419 BrFs::File(f) => out.push((prefix.to_string(), f.content_id)),
420 }
421 }
422 let reader = Brz::open(path)?.into_reader();
423 let reader = &*reader;
424 let mut files = Vec::new();
425 collect(&reader.get_fs()?, "", &mut files);
426 let mut out = BTreeMap::new();
427 for (p, content_id) in files {
428 let content = match content_id {
429 Some(id) => reader.find_blob(id)?.read()?,
430 None => Vec::new(),
431 };
432 out.insert(p, serde_json::json!({
433 "blake3": blake3::hash(&content).to_hex().to_string(),
434 "len": content.len(),
435 }));
436 }
437 Ok(out)
438}
439
440fn main() -> Result<(), Box<dyn std::error::Error>> {
441 let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures");
442 fs::create_dir_all(dir.join("schemas"))?;
443
444 let mut hashes = BTreeMap::new();
445 for (name, world) in [
446 ("brick", brick_world()),
447 ("features", features_world()),
448 ("chunks", chunks_world()),
449 ("wires", wires_world()),
450 ("components", components_world()),
451 ("entities", entities_world()),
452 ("spawner", spawner_world()),
453 ] {
454 let pending = world.to_unsaved()?.to_pending()?;
455 let raw_path = dir.join(format!("{name}_raw.brz"));
457 let mut f = fs::File::create(&raw_path)?;
458 pending.clone().to_brz_data(None)?.write(&mut f, None)?;
459 let mut f = fs::File::create(dir.join(format!("{name}.brz")))?;
461 pending.to_brz_data(Some(14))?.write(&mut f, Some(14))?;
462
463 let db_path = dir.join(format!("{name}.brdb"));
466 let _ = fs::remove_file(&db_path);
467 Brdb::create(&db_path)?.save("Fixture", &world)?;
468
469 hashes.insert(name.to_string(), hash_archive(&raw_path)?);
470 }
471 fs::write(dir.join("hashes.json"), serde_json::to_string_pretty(&hashes)?)?;
472
473 for (name, schema) in [
476 ("BRSavedGlobalDataSoA", schemas::global_data_schema()),
477 ("BRSavedOwnerTableSoA", schemas::owners_schema()),
478 ("BRSavedBrickChunkIndexSoA", schemas::bricks_chunk_index_schema()),
479 ("BRSavedBrickChunkSoA", schemas::bricks_chunks_schema()),
480 ("BRSavedWireChunkSoA", schemas::bricks_wires_schema()),
481 ("BRSavedComponentChunkSoA", schemas::bricks_components_schema_min()),
482 ("BRSavedComponentChunkSoA_max", schemas::bricks_components_schema_max()),
483 ("BRSavedEntityChunkIndexSoA", schemas::entities_chunk_index_schema()),
484 ("BRSavedEntityChunkSoA", schemas::entities_chunks_schema()),
485 ] {
486 fs::write(dir.join(format!("schemas/{name}.bin")), schema.to_bytes()?)?;
487 }
488 eprintln!("fixtures written to {}", dir.display());
489 Ok(())
490}