Ryan e03b7d7516
Updated C# Name attribute to Accessor (#4306)
# Description of Changes

Implementation of #4295

Convert existing `Name` attribute to `Accessor` to support new Canonical
Case conversation of 2.0

# API and ABI breaking changes

Yes, in C# modules, we no longer use the attribute name `Name`, it
should now be `Accessor`

# Expected complexity level and risk

1

# Testing

- [X] Build and tested locally
- [X] Ran regression tests locally
2026-02-17 01:16:16 +00:00

377 lines
11 KiB
C#

using SpacetimeDB;
namespace Benchmarks;
public static partial class ia_loop
{
[SpacetimeDB.Table(Accessor = "velocity")]
public partial struct Velocity(uint entity_id, float x, float y, float z)
{
[PrimaryKey]
public uint entity_id = entity_id;
public float x = x;
public float y = y;
public float z = z;
}
[SpacetimeDB.Table(Accessor = "position")]
public partial struct Position(uint entity_id, float x, float y, float z)
{
[PrimaryKey]
public uint entity_id = entity_id;
public float x = x;
public float y = y;
public float z = z;
public float vx = x + 10.0f;
public float vy = y + 20.0f;
public float vz = z + 30.0f;
}
public static ulong MomentMilliseconds()
{
// Idk why Rust uses complicated math, but it seems like it should always return 1.
return 1;
}
[SpacetimeDB.Type]
public enum AgentAction
{
Inactive,
Idle,
Evading,
Investigating,
Retreating,
Fighting,
}
[SpacetimeDB.Table(Accessor = "game_enemy_ai_agent_state")]
public partial struct GameEnemyAiAgentState(
ulong entity_id,
List<ulong> last_move_timestamps,
ulong next_action_timestamp,
AgentAction action
)
{
[PrimaryKey]
public ulong entity_id = entity_id;
public List<ulong> last_move_timestamps = last_move_timestamps;
public ulong next_action_timestamp = next_action_timestamp;
public AgentAction action = action;
}
[SpacetimeDB.Table(Accessor = "game_targetable_state")]
public partial struct GameTargetableState(ulong entity_id, long quad)
{
[PrimaryKey]
public ulong entity_id = entity_id;
public long quad = quad;
}
[SpacetimeDB.Table(Accessor = "game_live_targetable_state")]
public partial struct GameLiveTargetableState(ulong entity_id, long quad)
{
[Unique]
public ulong entity_id = entity_id;
[SpacetimeDB.Index.BTree]
public long quad = quad;
}
[SpacetimeDB.Table(Accessor = "game_mobile_entity_state")]
public partial struct GameMobileEntityState(
ulong entity_id,
int location_x,
int location_y,
ulong timestamp
)
{
[PrimaryKey]
public ulong entity_id = entity_id;
[SpacetimeDB.Index.BTree]
public int location_x = location_x;
public int location_y = location_y;
public ulong timestamp = timestamp;
}
[SpacetimeDB.Table(Accessor = "game_enemy_state")]
public partial struct GameEnemyState(ulong entity_id, int herd_id)
{
[PrimaryKey]
public ulong entity_id = entity_id;
public int herd_id = herd_id;
}
[SpacetimeDB.Type]
public partial struct SmallHexTile(int x, int z, uint dimension)
{
public int x = x;
public int z = z;
public uint dimension = dimension;
}
[SpacetimeDB.Table(Accessor = "game_herd_cache")]
public partial struct GameHerdCache(
int id,
uint dimension_id,
int current_population,
SmallHexTile location,
int max_population,
float spawn_eagerness,
int roaming_distance
)
{
[PrimaryKey]
public int id = id;
public uint dimension_id = dimension_id;
public int current_population = current_population;
public SmallHexTile location = location;
public int max_population = max_population;
public float spawn_eagerness = spawn_eagerness;
public int roaming_distance = roaming_distance;
}
[SpacetimeDB.Reducer]
public static void insert_bulk_position(ReducerContext ctx, uint count)
{
for (uint id = 0; id < count; id++)
{
ctx.Db.position.Insert(new(id, id, id + 5, id * 5));
}
Log.Info($"INSERT POSITION: {count}");
}
[SpacetimeDB.Reducer]
public static void insert_bulk_velocity(ReducerContext ctx, uint count)
{
for (uint id = 0; id < count; id++)
{
ctx.Db.velocity.Insert(new(id, id, id + 5, id * 5));
}
Log.Info($"INSERT VELOCITY: {count}");
}
[SpacetimeDB.Reducer]
public static void update_position_all(ReducerContext ctx, uint expected)
{
uint count = 0;
foreach (Position position in ctx.Db.position.Iter())
{
Position newPosition = position;
newPosition.x += position.vx;
newPosition.y += position.vy;
newPosition.z += position.vz;
ctx.Db.position.entity_id.Update(newPosition);
count++;
}
Log.Info($"UPDATE POSITION ALL: {expected}, processed: {count}");
}
[SpacetimeDB.Reducer]
public static void update_position_with_velocity(ReducerContext ctx, uint expected)
{
uint count = 0;
foreach (Velocity velocity in ctx.Db.velocity.Iter())
{
if (ctx.Db.position.entity_id.Find(velocity.entity_id) is not { } position)
{
continue;
}
position.x += velocity.x;
position.y += velocity.y;
position.z += velocity.z;
ctx.Db.position.entity_id.Update(position);
count++;
}
Log.Info($"UPDATE POSITION BY VELOCITY: {expected}, processed: {count}");
}
[SpacetimeDB.Reducer]
public static void insert_world(ReducerContext ctx, ulong players)
{
for (ulong i = 0; i < players; i++)
{
ulong next_action_timestamp =
(i & 2) == 2 ? MomentMilliseconds() + 2000 : MomentMilliseconds();
ctx.Db.game_enemy_ai_agent_state.Insert(
new(i, [i, 0, i * 2], next_action_timestamp, AgentAction.Idle)
);
ctx.Db.game_live_targetable_state.Insert(new(i, (long)i));
ctx.Db.game_targetable_state.Insert(new(i, (long)i));
ctx.Db.game_mobile_entity_state.Insert(new(i, (int)i, (int)i, next_action_timestamp));
ctx.Db.game_enemy_state.Insert(new(i, (int)i));
ctx.Db.game_herd_cache.Insert(
new(
(int)i,
(uint)i,
(int)(i * 2),
new SmallHexTile((int)i, (int)i, (uint)(i * 2)),
(int)(i * 4),
i,
(int)i
)
);
}
Log.Info($"INSERT WORLD PLAYERS: {players}");
}
public static List<GameTargetableState> GetTargetablesNearQuad(
ReducerContext ctx,
ulong entity_id,
ulong num_players
)
{
List<GameTargetableState> result = new(4);
for (ulong id = entity_id; id < num_players; id++)
{
foreach (
GameLiveTargetableState t in ctx.Db.game_live_targetable_state.quad.Filter((long)id)
)
{
result.Add(
ctx.Db.game_targetable_state.entity_id.Find(t.entity_id)
?? throw new Exception("Identity not found")
);
}
}
return result;
}
private const int MAX_MOVE_TIMESTAMPS = 20;
public static void MoveAgent(
ReducerContext ctx,
ref GameEnemyAiAgentState agent,
SmallHexTile agent_coord,
ulong current_time_ms
)
{
ulong entity_id = agent.entity_id;
GameEnemyState enemy =
ctx.Db.game_enemy_state.entity_id.Find(entity_id)
?? throw new Exception("GameEnemyState Entity ID not found");
ctx.Db.game_enemy_state.entity_id.Update(enemy);
agent.next_action_timestamp = current_time_ms + 2000;
agent.last_move_timestamps.Add(current_time_ms);
if (agent.last_move_timestamps.Count > MAX_MOVE_TIMESTAMPS)
{
agent.last_move_timestamps.RemoveAt(0);
}
GameTargetableState targetable =
ctx.Db.game_targetable_state.entity_id.Find(entity_id)
?? throw new Exception("GameTargetableState Entity ID not found");
int new_hash = targetable.quad.GetHashCode();
targetable.quad = new_hash;
ctx.Db.game_targetable_state.entity_id.Update(targetable);
if (ctx.Db.game_live_targetable_state.entity_id.Delete(entity_id))
{
ctx.Db.game_live_targetable_state.Insert(new(entity_id, new_hash));
}
GameMobileEntityState mobile_entity =
ctx.Db.game_mobile_entity_state.entity_id.Find(entity_id)
?? throw new Exception("GameMobileEntityState Entity ID not found");
mobile_entity.location_x += 1;
mobile_entity.location_y += 1;
mobile_entity.timestamp = agent.next_action_timestamp;
ctx.Db.game_enemy_ai_agent_state.entity_id.Update(agent);
ctx.Db.game_mobile_entity_state.entity_id.Update(mobile_entity);
}
public static void AgentLoop(
ReducerContext ctx,
GameEnemyAiAgentState agent,
GameTargetableState agent_targetable,
List<GameTargetableState> surrounding_agents,
ulong current_time_ms
)
{
ulong entity_id = agent.entity_id;
GameMobileEntityState? coordinates =
ctx.Db.game_mobile_entity_state.entity_id.Find(entity_id)
?? throw new Exception("GameMobileEntityState Entity ID not found");
GameEnemyState agent_entity =
ctx.Db.game_enemy_state.entity_id.Find(entity_id)
?? throw new Exception("GameEnemyState Entity ID not found");
GameHerdCache agent_herd =
ctx.Db.game_herd_cache.id.Find(agent_entity.herd_id)
?? throw new Exception("GameHerdCache Entity ID not found");
SmallHexTile agent_herd_coordinates = agent_herd.location;
MoveAgent(ctx, ref agent, agent_herd_coordinates, current_time_ms);
}
[SpacetimeDB.Reducer]
public static void game_loop_enemy_ia(ReducerContext ctx, ulong players)
{
uint count = 0;
ulong current_time_ms = MomentMilliseconds();
foreach (GameEnemyAiAgentState agent in ctx.Db.game_enemy_ai_agent_state.Iter())
{
GameTargetableState agent_targetable =
ctx.Db.game_targetable_state.entity_id.Find(agent.entity_id)
?? throw new Exception("No TargetableState for AgentState entity");
List<GameTargetableState> surrounding_agents = GetTargetablesNearQuad(
ctx,
agent_targetable.entity_id,
players
);
GameEnemyAiAgentState newAgent = agent with { action = AgentAction.Fighting };
AgentLoop(ctx, newAgent, agent_targetable, surrounding_agents, current_time_ms);
count++;
}
Log.Info($"ENEMY IA LOOP PLAYERS: {players}, processed: {count}");
}
[SpacetimeDB.Reducer]
public static void init_game_ia_loop(ReducerContext ctx, uint initial_load)
{
Load load = new(initial_load);
insert_bulk_position(ctx, load.biggest_table);
insert_bulk_velocity(ctx, load.big_table);
update_position_all(ctx, load.biggest_table);
update_position_with_velocity(ctx, load.big_table);
insert_world(ctx, load.num_players);
}
[SpacetimeDB.Reducer]
public static void run_game_ia_loop(ReducerContext ctx, uint initial_load)
{
Load load = new(initial_load);
game_loop_enemy_ia(ctx, load.num_players);
}
}