Files
Mazdak Farrokhzad 50d58a2858 Add modules/benchmarks-ts (#3408)
# Description of Changes

Adds `modules/benchmarks-ts`.

# API and ABI breaking changes

None

# Expected complexity level and risk

2?

# Testing

This is a test.

---------

Co-authored-by: Phoebe Goldman <phoebe@goldman-tribe.org>
Co-authored-by: Noa <coolreader18@gmail.com>
2025-10-24 16:34:13 +00:00

175 lines
4.4 KiB
Rust

//! STDB module used for benchmarks based on "realistic" workloads we are focusing in improving.
use crate::Load;
use spacetimedb::{log, ReducerContext, SpacetimeType, Table, Timestamp};
use std::hint::black_box;
#[derive(SpacetimeType, Debug, Clone, Copy)]
pub struct Vector2 {
pub x: f32,
pub y: f32,
}
// ---------- schemas ----------
#[spacetimedb::table(name = entity)]
pub struct Entity {
#[auto_inc]
#[primary_key]
pub id: u32,
pub position: Vector2,
pub mass: u32,
}
impl Entity {
pub fn new(id: u32, x: f32, y: f32, mass: u32) -> Self {
Self {
id,
mass,
position: Vector2 { x, y },
}
}
}
#[spacetimedb::table(name = circle)]
pub struct Circle {
#[primary_key]
pub entity_id: u32,
#[index(btree)]
pub player_id: u32,
pub direction: Vector2,
pub magnitude: f32,
pub last_split_time: Timestamp,
}
impl Circle {
pub fn new(entity_id: u32, player_id: u32, x: f32, y: f32, magnitude: f32, last_split_time: Timestamp) -> Self {
Self {
entity_id,
player_id,
direction: Vector2 { x, y },
magnitude,
last_split_time,
}
}
}
#[spacetimedb::table(name = food)]
pub struct Food {
#[primary_key]
pub entity_id: u32,
}
impl Food {
pub fn new(entity_id: u32) -> Self {
Self { entity_id }
}
}
fn mass_to_radius(mass: u32) -> f32 {
(mass as f32).sqrt()
}
fn is_overlapping(entity1: &Entity, entity2: &Entity) -> bool {
let entity1_radius = mass_to_radius(entity1.mass);
let entity2_radius = mass_to_radius(entity2.mass);
let distance =
((entity1.position.x - entity2.position.x).powi(2) + (entity1.position.y - entity2.position.y).powi(2)).sqrt();
distance < entity1_radius.max(entity2_radius)
}
// ---------- insert bulk ----------
#[spacetimedb::reducer]
pub fn insert_bulk_entity(ctx: &ReducerContext, count: u32) {
for id in 0..count {
ctx.db
.entity()
.insert(Entity::new(0, id as f32, (id + 5) as f32, id * 5));
}
log::info!("INSERT ENTITY: {count}");
}
#[spacetimedb::reducer]
pub fn insert_bulk_circle(ctx: &ReducerContext, count: u32) {
for id in 0..count {
ctx.db.circle().insert(Circle::new(
id,
id,
id as f32,
(id + 5) as f32,
(id * 5) as f32,
ctx.timestamp,
));
}
log::info!("INSERT CIRCLE: {count}");
}
#[spacetimedb::reducer]
pub fn insert_bulk_food(ctx: &ReducerContext, count: u32) {
for id in 1..=count {
ctx.db.food().insert(Food::new(id));
}
log::info!("INSERT FOOD: {count}");
}
// Simulate
// ```
// SELECT * FROM Circle, Entity, Food
// ```
#[spacetimedb::reducer]
pub fn cross_join_all(ctx: &ReducerContext, expected: u32) {
let mut count = 0;
for _circle in ctx.db.circle().iter() {
for _entity in ctx.db.entity().iter() {
for _food in ctx.db.food().iter() {
count += 1;
}
}
}
log::info!("CROSS JOIN ALL: {expected}, processed: {count}");
}
// Simulate
// ```
// SELECT * FROM Circle JOIN ENTITY USING(entity_id), Food JOIN ENTITY USING(entity_id)
// ```
#[spacetimedb::reducer]
pub fn cross_join_circle_food(ctx: &ReducerContext, expected: u32) {
let mut count = 0;
for circle in ctx.db.circle().iter() {
let Some(circle_entity) = ctx.db.entity().id().find(circle.entity_id) else {
continue;
};
for food in ctx.db.food().iter() {
count += 1;
let food_entity = ctx
.db
.entity()
.id()
.find(food.entity_id)
.unwrap_or_else(|| panic!("Entity not found: {}", food.entity_id));
black_box(is_overlapping(&circle_entity, &food_entity));
}
}
log::info!("CROSS JOIN CIRCLE FOOD: {expected}, processed: {count}");
}
#[spacetimedb::reducer]
pub fn init_game_circles(ctx: &ReducerContext, initial_load: u32) {
let load = Load::new(initial_load);
insert_bulk_food(ctx, load.initial_load);
insert_bulk_entity(ctx, load.initial_load);
insert_bulk_circle(ctx, load.small_table);
}
#[spacetimedb::reducer]
pub fn run_game_circles(ctx: &ReducerContext, initial_load: u32) {
let load = Load::new(initial_load);
cross_join_circle_food(ctx, initial_load * load.small_table);
cross_join_all(ctx, initial_load * initial_load * load.small_table);
}