mirror of
https://github.com/clockworklabs/SpacetimeDB.git
synced 2026-05-11 18:36:15 -04:00
52b6c66fa1
# Description of Changes This adds C++ server bindings (/crate/bindings-cpp) to allow writing C++ 20 modules. - Emscripten WASM build system integration with CMake - Macro-based code generation (SPACETIMEDB_TABLE, SPACETIMEDB_REDUCER, etc) - All SpacetimeDB types supported (primitives, Timestamp, Identity, Uuid, etc) - Product types via SPACETIMEDB_STRUCT - Sum types via SPACETIMEDB_ENUM - Constraints marked with FIELD* macros # API and ABI breaking changes None # Expected complexity level and risk 2 - Doesn't heavily impact any other areas but is complex macro C++ structure to support a similar developer experience, did have a small impact on init command # Testing - [x] modules/module-test-cpp - heavily tested every reducer - [x] modules/benchmarks-cpp - tested through the standalone (~6x faster than C#, ~6x slower than Rust) - [x] modules/sdk-test-cpp - [x] modules/sdk-test-procedure-cpp - [x] modules/sdk-test-view-cpp - [x] Wrote several test modules myself - [x] Quickstart smoketest [Currently in progress] - [ ] Write Blackholio C++ server module --------- Signed-off-by: Jason Larabie <jason@clockworklabs.io> Co-authored-by: clockwork-labs-bot <clockwork-labs-bot@users.noreply.github.com> Co-authored-by: Ryan <r.ekhoff@clockworklabs.io> Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
202 lines
7.3 KiB
C++
202 lines
7.3 KiB
C++
//! STDB module used for benchmarks based on "realistic" workloads we are focusing in improving.
|
|
//! Circles benchmark - Game-like entities with spatial queries
|
|
|
|
#include "common.h"
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
// =============================================================================
|
|
// CIRCLES BENCHMARK - DATA STRUCTURES
|
|
// =============================================================================
|
|
|
|
// Vector2 - 2D coordinate structure
|
|
struct Vector2 {
|
|
float x;
|
|
float y;
|
|
};
|
|
SPACETIMEDB_STRUCT(Vector2, x, y)
|
|
|
|
// Entity table - represents game objects with position and mass
|
|
struct Entity {
|
|
uint32_t id;
|
|
Vector2 position;
|
|
uint32_t mass;
|
|
};
|
|
SPACETIMEDB_STRUCT(Entity, id, position, mass)
|
|
SPACETIMEDB_TABLE(Entity, entity, Public)
|
|
FIELD_PrimaryKeyAutoInc(entity, id)
|
|
|
|
// Circle table - represents player-controlled entities
|
|
struct Circle {
|
|
uint32_t entity_id;
|
|
uint32_t player_id;
|
|
Vector2 direction;
|
|
float magnitude;
|
|
Timestamp last_split_time;
|
|
};
|
|
SPACETIMEDB_STRUCT(Circle, entity_id, player_id, direction, magnitude, last_split_time)
|
|
SPACETIMEDB_TABLE(Circle, circle, Public)
|
|
FIELD_PrimaryKey(circle, entity_id)
|
|
FIELD_Index(circle, player_id)
|
|
|
|
// Food table - represents consumable game objects
|
|
struct Food {
|
|
uint32_t entity_id;
|
|
};
|
|
SPACETIMEDB_STRUCT(Food, entity_id)
|
|
SPACETIMEDB_TABLE(Food, food, Public)
|
|
FIELD_PrimaryKey(food, entity_id)
|
|
|
|
// =============================================================================
|
|
// CIRCLES BENCHMARK - HELPER FUNCTIONS
|
|
// =============================================================================
|
|
|
|
// Convert mass to radius for collision detection
|
|
inline float mass_to_radius(uint32_t mass) {
|
|
return std::sqrt(static_cast<float>(mass));
|
|
}
|
|
|
|
// Check if two entities are overlapping based on their positions and masses
|
|
inline bool is_overlapping(const Entity& entity1, const Entity& entity2) {
|
|
float entity1_radius = mass_to_radius(entity1.mass);
|
|
float entity2_radius = mass_to_radius(entity2.mass);
|
|
float dx = entity1.position.x - entity2.position.x;
|
|
float dy = entity1.position.y - entity2.position.y;
|
|
float distance = std::sqrt(dx * dx + dy * dy);
|
|
return distance < std::max(entity1_radius, entity2_radius);
|
|
}
|
|
|
|
// =============================================================================
|
|
// CIRCLES BENCHMARK - BULK INSERT OPERATIONS
|
|
// =============================================================================
|
|
|
|
// Bulk insert entities with auto-incremented IDs
|
|
SPACETIMEDB_REDUCER(insert_bulk_entity, ReducerContext& ctx, uint32_t count) {
|
|
for (uint32_t id = 0; id < count; ++id) {
|
|
Entity new_entity = {
|
|
0, // Auto-incremented by database
|
|
{static_cast<float>(id), static_cast<float>(id + 5)}, // position
|
|
id * 5 // mass
|
|
};
|
|
ctx.db[entity].insert(new_entity);
|
|
}
|
|
LOG_INFO("INSERT ENTITY: " + std::to_string(count));
|
|
return Ok();
|
|
}
|
|
|
|
// Bulk insert circles with specified entity and player IDs
|
|
SPACETIMEDB_REDUCER(insert_bulk_circle, ReducerContext& ctx, uint32_t count) {
|
|
for (uint32_t id = 0; id < count; ++id) {
|
|
Circle new_circle = {
|
|
id, // entity_id
|
|
id, // player_id
|
|
{static_cast<float>(id), static_cast<float>(id + 5)}, // direction
|
|
static_cast<float>(id * 5), // magnitude
|
|
ctx.timestamp // last_split_time
|
|
};
|
|
ctx.db[circle].insert(new_circle);
|
|
}
|
|
LOG_INFO("INSERT CIRCLE: " + std::to_string(count));
|
|
return Ok();
|
|
}
|
|
|
|
// Bulk insert food entities
|
|
SPACETIMEDB_REDUCER(insert_bulk_food, ReducerContext& ctx, uint32_t count) {
|
|
for (uint32_t id = 1; id <= count; ++id) {
|
|
Food new_food = {id};
|
|
ctx.db[food].insert(new_food);
|
|
}
|
|
LOG_INFO("INSERT FOOD: " + std::to_string(count));
|
|
return Ok();
|
|
}
|
|
|
|
// =============================================================================
|
|
// CIRCLES BENCHMARK - CROSS JOIN OPERATIONS
|
|
// =============================================================================
|
|
|
|
// Simulate: SELECT * FROM Circle, Entity, Food
|
|
// This creates a Cartesian product of all three tables
|
|
SPACETIMEDB_REDUCER(cross_join_all, ReducerContext& ctx, uint32_t expected) {
|
|
uint32_t count = 0;
|
|
for (const auto& _circle : ctx.db[circle]) {
|
|
for (const auto& _entity : ctx.db[entity]) {
|
|
for (const auto& _food : ctx.db[food]) {
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
LOG_INFO("CROSS JOIN ALL: " + std::to_string(expected) + ", processed: " + std::to_string(count));
|
|
return Ok();
|
|
}
|
|
|
|
// Simulate: SELECT * FROM Circle JOIN Entity USING(entity_id), Food JOIN Entity USING(entity_id)
|
|
// This joins circles with their entities, then cross-joins with food entities to check overlaps
|
|
SPACETIMEDB_REDUCER(cross_join_circle_food, ReducerContext& ctx, uint32_t expected) {
|
|
uint32_t count = 0;
|
|
for (const auto& circle_elem : ctx.db[circle]) {
|
|
// Find the entity for this circle
|
|
auto circle_entity_opt = ctx.db[entity_id].find(circle_elem.entity_id);
|
|
if (!circle_entity_opt) {
|
|
continue; // Skip if entity not found
|
|
}
|
|
const auto& circle_entity = *circle_entity_opt;
|
|
|
|
// Cross join with all food entities
|
|
for (const auto& food_elem : ctx.db[food]) {
|
|
++count;
|
|
// Find the entity for this food
|
|
auto food_entity_opt = ctx.db[entity_id].find(food_elem.entity_id);
|
|
if (!food_entity_opt) {
|
|
return Err("Entity not found: " + std::to_string(food_elem.entity_id));
|
|
}
|
|
const auto& food_entity = *food_entity_opt;
|
|
|
|
// Check overlap and use black_box to prevent optimization
|
|
black_box(is_overlapping(circle_entity, food_entity));
|
|
}
|
|
}
|
|
LOG_INFO("CROSS JOIN CIRCLE FOOD: " + std::to_string(expected) + ", processed: " + std::to_string(count));
|
|
return Ok();
|
|
}
|
|
|
|
// =============================================================================
|
|
// CIRCLES BENCHMARK - GAME SIMULATION ENTRY POINTS
|
|
// =============================================================================
|
|
|
|
// Initialize the circles game simulation with test data
|
|
SPACETIMEDB_REDUCER(init_game_circles, ReducerContext& ctx, uint32_t initial_load) {
|
|
Load load(initial_load);
|
|
|
|
// Set up the game world with food, entities, and circles
|
|
auto bulk_food_res = insert_bulk_food(ctx, load.initial_load);
|
|
if (bulk_food_res.is_err()) {
|
|
return bulk_food_res;
|
|
}
|
|
auto bulk_entity_res = insert_bulk_entity(ctx, load.initial_load);
|
|
if (bulk_entity_res.is_err()) {
|
|
return bulk_entity_res;
|
|
}
|
|
auto bulk_circle_res = insert_bulk_circle(ctx, load.small_table);
|
|
if (bulk_circle_res.is_err()) {
|
|
return bulk_circle_res;
|
|
}
|
|
return Ok();
|
|
}
|
|
|
|
// Run the circles game simulation benchmark
|
|
SPACETIMEDB_REDUCER(run_game_circles, ReducerContext& ctx, uint32_t initial_load) {
|
|
Load load(initial_load);
|
|
|
|
// Perform the main benchmark operations
|
|
auto cross_join_circle_food_res = cross_join_circle_food(ctx, initial_load * load.small_table);
|
|
if (cross_join_circle_food_res.is_err()) {
|
|
return cross_join_circle_food_res;
|
|
}
|
|
auto cross_join_all_res = cross_join_all(ctx, initial_load * initial_load * load.small_table);
|
|
if (cross_join_all_res.is_err()) {
|
|
return cross_join_all_res;
|
|
}
|
|
return Ok();
|
|
}
|
|
|