diff --git a/publish-local.sh b/publish-local.sh deleted file mode 100755 index e1ad150..0000000 --- a/publish-local.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -spacetime logout -spacetime login --server-issued-login local -spacetime publish -y --server local --bin-path=zig-out/bin/stdb-zig-helloworld.wasm -DB_HASH=$(spacetime list 2>/dev/null | tail -1) -spacetime logs $DB_HASH \ No newline at end of file diff --git a/spacetimedb.sh b/spacetimedb.sh index 0fb7438..f40bbc2 100755 --- a/spacetimedb.sh +++ b/spacetimedb.sh @@ -9,7 +9,7 @@ if [[ "$func" == "publish" ]]; then spacetime login --server-issued-login local spacetime publish -y --server local --bin-path=zig-out/bin/blackholio.wasm blackholio DB_HASH=$(spacetime list 2>/dev/null | tail -1) - spacetime logs $DB_HASH + spacetime logs $DB_HASH -n 15 exit $? fi diff --git a/src/main.zig b/src/main.zig index e286548..eab0155 100644 --- a/src/main.zig +++ b/src/main.zig @@ -188,6 +188,9 @@ pub const spacespec = spacetime.Spec{ .params = &.{ "_timer", }, }) }, + .row_level_security = &.{ + "SELECT * FROM logged_out_player WHERE identity = :sender" + } }; pub const DbVector2 = struct { @@ -356,6 +359,7 @@ pub fn disconnect(ctx: *spacetime.ReducerContext) !void { // Remove any circles from the arena var iter = try ctx.db.get("circle").col("player_id").filter(.{ .player_id = player.player_id }); + defer iter.close(); while (try iter.next()) |circle_val| { try ctx.db.get("entity").col("entity_id").delete(.{ .entity_id = circle_val.entity_id, }); try ctx.db.get("circle").col("entity_id").delete(.{ .entity_id = circle_val.entity_id, }); @@ -435,6 +439,7 @@ pub fn suicide(ctx: *spacetime.ReducerContext) !void { .find(.{ .identity = ctx.sender})).?; var circles = try ctx.db.get("circle").col("player_id").filter(.{ .player_id = player.player_id}); + defer circles.close(); while(try circles.next()) |circle| { try destroy_entity(ctx, circle.entity_id); @@ -450,6 +455,7 @@ pub fn update_player_input(ctx: *spacetime.ReducerContext, direction: DbVector2) .col("identity") .find(.{ .identity = ctx.sender})).?; var circles = try ctx.db.get("circle").col("player_id").filter(.{ .player_id = player.player_id}); + defer circles.close(); while(try circles.next()) |circle| { var copy_circle = circle; copy_circle.direction = direction.normalized(); @@ -491,22 +497,26 @@ pub fn move_all_players(ctx: *spacetime.ReducerContext, _timer: MoveAllPlayersTi .db.get("config").col("id") .find(.{ .id = 0 })).?.world_size; - var circle_directions = std.AutoHashMap(u32, DbVector2).init(ctx.db.allocator); - var circleIter = ctx.db.get("circle").iter(); + var circle_directions = std.AutoHashMap(u32, DbVector2).init(ctx.allocator); + var circleIter = try ctx.db.get("circle").iter(); + defer circleIter.close(); while(try circleIter.next()) |circle| { try circle_directions.put(circle.entity_id, circle.direction.scale(circle.speed)); } - var playerIter = ctx.db.get("player").iter(); + var playerIter = try ctx.db.get("player").iter(); + defer playerIter.close(); + while(try playerIter.next()) |player| { - var circles = std.ArrayList(Circle).init(ctx.db.allocator); + var circles = std.ArrayList(Circle).init(ctx.allocator); var circlesIter1 = try ctx.db.get("circle").col("player_id") .filter(.{ .player_id = player.player_id}); + defer circlesIter1.close(); while(try circlesIter1.next()) |circle| { try circles.append(circle); } - var player_entities = std.ArrayList(Entity).init(ctx.db.allocator); + var player_entities = std.ArrayList(Entity).init(ctx.allocator); for(circles.items) |c| { try player_entities.append((try ctx.db.get("entity").col("entity_id").find(.{ .entity_id = c.entity_id})).?); } @@ -575,7 +585,8 @@ pub fn move_all_players(ctx: *spacetime.ReducerContext, _timer: MoveAllPlayersTi } } - var circleIter2 = ctx.db.get("circle").iter(); + var circleIter2 = try ctx.db.get("circle").iter(); + defer circleIter2.close(); while(try circleIter2.next()) |circle| { const circle_entity_n = (ctx.db.get("entity").col("entity_id").find(.{ .entity_id = circle.entity_id }) catch { continue; @@ -586,22 +597,24 @@ pub fn move_all_players(ctx: *spacetime.ReducerContext, _timer: MoveAllPlayersTi const new_pos = circle_entity.position.add(direction.scale(mass_to_max_move_speed(circle_entity.mass))); const min = circle_radius; const max = @as(f32, @floatFromInt(world_size)) - circle_radius; + if(max < min) continue; circle_entity.position.x = std.math.clamp(new_pos.x, min, max); circle_entity.position.y = std.math.clamp(new_pos.y, min, max); try ctx.db.get("entity").col("entity_id").update(circle_entity); } // Check collisions - var entities = std.AutoHashMap(u32, Entity).init(ctx.db.allocator); - var entitiesIter = ctx.db.get("entity").iter(); + var entities = std.AutoHashMap(u32, Entity).init(ctx.allocator); + var entitiesIter = try ctx.db.get("entity").iter(); + defer entitiesIter.close(); while(try entitiesIter.next()) |e| { try entities.put(e.entity_id, e); } - var circleIter3 = ctx.db.get("circle").iter(); + var circleIter3 = try ctx.db.get("circle").iter(); + defer circleIter3.close(); while(try circleIter3.next()) |circle| { // let span = spacetimedb::time_span::Span::start("collisions"); var circle_entity = entities.get(circle.entity_id).?; - _ = &circle_entity; var entityIter = entities.iterator(); while (entityIter.next()) |other_entity| { if(other_entity.value_ptr.entity_id == circle_entity.entity_id) { @@ -670,12 +683,13 @@ pub fn player_split(ctx: *spacetime.ReducerContext) !void { const player = (try ctx .db.get("player").col("identity") .find(.{ .identity = ctx.sender})).?; - var circles = std.ArrayList(Circle).init(ctx.db.allocator); + var circles = std.ArrayList(Circle).init(ctx.allocator); var circlesIter = try ctx .db .get("circle") .col("player_id") .filter(.{ .player_id = player.player_id}); + defer circlesIter.close(); while(try circlesIter.next()) |circle| { try circles.append(circle); } @@ -756,7 +770,8 @@ pub fn spawn_food(ctx: *spacetime.ReducerContext, _: SpawnFoodTimer) !void { } pub fn circle_decay(ctx: *spacetime.ReducerContext, _: CircleDecayTimer) !void { - var circleIter = ctx.db.get("circle").iter(); + var circleIter = try ctx.db.get("circle").iter(); + defer circleIter.close(); while(try circleIter.next()) |circle| { var circle_entity = (try ctx .db @@ -779,7 +794,6 @@ pub fn calculate_center_of_mass(entities: []const Entity) DbVector2 { } break :blk sum; }; - //entities.iter().map(|e| e.position * e.mass as f32).sum(); const center_of_mass: DbVector2 = blk: { var sum: DbVector2 = 0; for(entities) |entity| { @@ -792,16 +806,17 @@ pub fn calculate_center_of_mass(entities: []const Entity) DbVector2 { } pub fn circle_recombine(ctx: *spacetime.ReducerContext, timer: CircleRecombineTimer) !void { - var circles = std.ArrayList(Circle).init(ctx.db.allocator); + var circles = std.ArrayList(Circle).init(ctx.allocator); var circlesIter = try ctx .db .get("circle") .col("player_id") .filter(.{ .player_id = timer.player_id }); + defer circlesIter.close(); while(try circlesIter.next()) |circle| { try circles.append(circle); } - var recombining_entities = std.ArrayList(Entity).init(ctx.db.allocator); + var recombining_entities = std.ArrayList(Entity).init(ctx.allocator); for(circles.items) |circle| { if(@as(f32, @floatFromInt(ctx.timestamp.__timestamp_micros_since_unix_epoch__ - circle.last_split_time.__timestamp_micros_since_unix_epoch__)) >= SPLIT_RECOMBINE_DELAY_SEC) { const entity = (try ctx.db diff --git a/src/spacetime.zig b/src/spacetime.zig index 8d9b7bb..9b6cba7 100644 --- a/src/spacetime.zig +++ b/src/spacetime.zig @@ -64,7 +64,13 @@ pub fn logFn(comptime level: std.log.Level, comptime _: @TypeOf(.enum_literal), pub const BytesSink = extern struct { inner: u32 }; pub const BytesSource = extern struct { inner: u32 }; pub const TableId = extern struct { _inner: u32, }; -pub const RowIter = extern struct { _inner: u32, pub const INVALID = RowIter{ ._inner = 0}; }; +pub const RowIter = extern struct { + _inner: u32, + pub const INVALID = RowIter{ ._inner = 0}; + pub fn invalid(self: @This()) bool { + return self._inner == 0; + } +}; pub const IndexId = extern struct{ _inner: u32 }; pub const ColId = extern struct { _inner: u16 }; @@ -121,6 +127,7 @@ pub const SpacetimeValue = enum(u1) { }; pub const SpacetimeError = error { + UNKNOWN, HOST_CALL_FAILURE, NOT_IN_TRANSACTION, BSATN_DECODE_ERROR, @@ -586,6 +593,7 @@ pub const Table = struct { pub const Spec = struct { tables: []const Table, reducers: []const SpecReducer, + row_level_security: []const []const u8, includes: []const Spec = &.{}, }; @@ -598,6 +606,8 @@ pub fn SpecBuilder(comptime spec: Spec) RawModuleDefV9 { var raw_types: []const AlgebraicType = &[_]AlgebraicType{}; var types: []const RawTypeDefV9 = &[_]RawTypeDefV9{}; + var row_level_security: []const RawRowLevelSecurityDefV9 = &[_]RawRowLevelSecurityDefV9{}; + var structDecls: []const StructImpl = &[_]StructImpl{}; for(spec.tables) |table| { @@ -773,6 +783,14 @@ pub fn SpecBuilder(comptime spec: Spec) RawModuleDefV9 { }; } + for(spec.row_level_security) |rls| { + row_level_security = row_level_security ++ &[_]RawRowLevelSecurityDefV9{ + RawRowLevelSecurityDefV9{ + .sql = rls, + } + }; + } + return .{ .typespace = .{ .types = raw_types, @@ -781,7 +799,7 @@ pub fn SpecBuilder(comptime spec: Spec) RawModuleDefV9 { .reducers = reducerDefs, .types = types, .misc_exports = &[_]RawMiscModuleExportV9{}, - .row_level_security = &[_]RawRowLevelSecurityDefV9{}, + .row_level_security = row_level_security, }; } } @@ -830,14 +848,19 @@ pub export fn __call_reducer__( ) i16 { _ = err; - const allocator = std.heap.wasm_allocator; + const backend_allocator = std.heap.wasm_allocator; + var arena_allocator = std.heap.ArenaAllocator.init(backend_allocator); + defer arena_allocator.deinit(); + const allocator = arena_allocator.allocator(); var ctx: ReducerContext = .{ + .allocator = allocator, .sender = std.mem.bytesAsValue(Identity, std.mem.sliceAsBytes(&[_]u64{ sender_0, sender_1, sender_2, sender_3})).*, .timestamp = Timestamp{ .__timestamp_micros_since_unix_epoch__ = @intCast(timestamp), }, .connection_id = std.mem.bytesAsValue(ConnectionId, std.mem.sliceAsBytes(&[_]u64{ conn_id_0, conn_id_1})).*, .db = .{ - .allocator = allocator, + .allocator = backend_allocator, + .frame_allocator = allocator, }, }; diff --git a/src/spacetime/serializer.zig b/src/spacetime/serializer.zig index a76462e..4f1926d 100644 --- a/src/spacetime/serializer.zig +++ b/src/spacetime/serializer.zig @@ -109,9 +109,8 @@ fn serialize_raw_misc_module_export_v9(array: *std.ArrayList(u8), val: RawMiscMo } fn serialize_raw_row_level_security_def_v9(array: *std.ArrayList(u8), val: RawRowLevelSecurityDefV9) !void { - _ = array; - _ = val; - unreachable; + try array.appendSlice(&std.mem.toBytes(@as(u32, @intCast(val.sql.len)))); + try array.appendSlice(val.sql); } fn serialize_raw_index_algorithm(array: *std.ArrayList(u8), val: RawIndexAlgorithm) !void { diff --git a/src/spacetime/types.zig b/src/spacetime/types.zig index d91a524..64f1165 100644 --- a/src/spacetime/types.zig +++ b/src/spacetime/types.zig @@ -302,7 +302,6 @@ pub fn StructDeserializer(struct_type: type) fn(allocator: std.mem.Allocator, *[ i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, i256, u256, f32, f64 => { - //std.log.debug("field_type: {} (offset_mem.len: {})", .{field.type, offset_mem.len}); @field(ret, field.name) = std.mem.bytesAsValue(field.type, offset_mem[0..@sizeOf(field.type)]).*; offset_mem = offset_mem[@sizeOf(field.type)..]; }, @@ -341,44 +340,74 @@ pub fn Iter(struct_type: type) type { return struct { allocator: std.mem.Allocator, handle: spacetime.RowIter, - buffer: [0x5_000]u8 = undefined, - contents: ?[]u8 = null, + buffer: []u8, + contents: []u8, last_ret: SpacetimeValue = .OK, + inited: bool = false, + pub fn init(allocator: std.mem.Allocator, rowIter: spacetime.RowIter) !@This() { + const buffer = try allocator.alloc(u8, 0x20_000); + return .{ + .allocator = allocator, + .handle = rowIter, + .buffer = buffer, + .contents = buffer[0..0], + .inited = true, + }; + } + pub fn next(self: *@This()) spacetime.ReducerError!?struct_type { var buffer_len: usize = undefined; var ret: spacetime.SpacetimeValue = self.last_ret; - if(self.contents == null or self.contents.?.len == 0) { - if(self.handle._inner == spacetime.RowIter.INVALID._inner) { - self.contents = null; + blk: while(true) { + if(self.contents.len == 0) { + if(self.handle._inner == spacetime.RowIter.INVALID._inner) { + return null; + } + + buffer_len = self.buffer.len; + ret = spacetime.retMap(spacetime.row_iter_bsatn_advance(self.handle, self.buffer.ptr, &buffer_len)) catch |err| { + switch(err) { + SpacetimeError.BUFFER_TOO_SMALL => { + self.buffer = try self.allocator.realloc(self.buffer, buffer_len); + continue :blk; + }, + SpacetimeError.NO_SUCH_ITER => { + return SpacetimeError.NO_SUCH_ITER; + }, + else => { + return SpacetimeError.UNKNOWN; + } + } + }; + + self.contents = self.buffer[0..buffer_len]; + + if(ret == .EXHAUSTED) { + self.handle = spacetime.RowIter.INVALID; + } + self.last_ret = ret; + } + if(self.contents.len == 0) { return null; } - - buffer_len = self.buffer.len; - ret = try spacetime.retMap(spacetime.row_iter_bsatn_advance(self.handle, @constCast(@ptrCast(&self.buffer)), &buffer_len)); - self.contents = self.buffer[0..buffer_len]; - if(ret == .EXHAUSTED) { - self.handle = spacetime.RowIter.INVALID; - } - self.last_ret = ret; - } - if(self.contents == null or self.contents.?.len == 0) { - return null; - } + var offset = self.contents; + const retValue = try StructDeserializer(struct_type)(self.allocator, &offset); + self.contents = offset; - return try StructDeserializer(struct_type)(self.allocator, &(self.contents.?)); - } - - pub fn one_or_null(self: *@This()) ?struct_type { - defer self.close(); - return self.next() catch null; + return retValue; + } } pub fn close(self: *@This()) void { - _ = spacetime.row_iter_bsatn_close(self.handle); - self.handle = spacetime.RowIter.INVALID; - self.contents = null; + if (self.handle.invalid()) + { + _ = spacetime.row_iter_bsatn_close(self.handle); + self.handle = spacetime.RowIter.INVALID; + } + self.contents = undefined; + self.allocator.free(self.buffer); } }; } @@ -429,9 +458,9 @@ pub fn Column2ORM(comptime table_name: []const u8, comptime column_name: [:0]con }; const size: usize = getStructSize(nVal); - const mem = try self.allocator.alloc(u8, size); - var offset_mem = mem; + const mem = try self.allocator.alignedAlloc(u8, 1, size); defer self.allocator.free(mem); + var offset_mem = mem; getStructData(nVal, &offset_mem); const data = mem[0..size]; @@ -449,15 +478,12 @@ pub fn Column2ORM(comptime table_name: []const u8, comptime column_name: [:0]con &rowIter )); - return Iter(struct_type){ - .allocator = self.allocator, - .handle = rowIter, - }; + return Iter(struct_type).init(self.allocator, rowIter); } pub fn find(self: @This(), val: wrapped_type) !?struct_type { var iter = try self.filter(val); - return iter.one_or_null(); + return try iter.next(); } pub fn delete(self: @This(), val: wrapped_type) !void { @@ -472,8 +498,8 @@ pub fn Column2ORM(comptime table_name: []const u8, comptime column_name: [:0]con const size: usize = getStructSize(nVal); const mem = try self.allocator.alloc(u8, size); - var offset_mem = mem; defer self.allocator.free(mem); + var offset_mem = mem; getStructData(nVal, &offset_mem); const data = mem[0..size]; @@ -502,8 +528,8 @@ pub fn Column2ORM(comptime table_name: []const u8, comptime column_name: [:0]con const size: usize = getStructSize(val); const mem = try self.allocator.alloc(u8, size); - var offset_mem = mem; defer self.allocator.free(mem); + var offset_mem = mem; getStructData(val, &offset_mem); const data = mem[0..size]; @@ -579,15 +605,12 @@ pub fn Table2ORM(comptime table_name: []const u8) type { return data_copy; } - pub fn iter(self: @This()) Iter(struct_type) { + pub fn iter(self: @This()) !Iter(struct_type) { var id: TableId = undefined; _ = spacetime.table_id_from_name(table_name.ptr, table_name.len, &id); var rowIter: spacetime.RowIter = undefined; _ = spacetime.datastore_table_scan_bsatn(id, &rowIter); - return Iter(struct_type){ - .allocator = self.allocator, - .handle = rowIter, - }; + return Iter(struct_type).init(self.allocator, rowIter); } pub fn col(self: @This(), comptime column_name: [:0]const u8) Column2ORM(table_name, column_name) { @@ -609,15 +632,17 @@ pub fn Table2ORM(comptime table_name: []const u8) type { pub const Local = struct { allocator: std.mem.Allocator, + frame_allocator: std.mem.Allocator, pub fn get(self: @This(), comptime table: []const u8) Table2ORM(table) { return .{ - .allocator = self.allocator, + .allocator = self.frame_allocator, }; } }; pub const ReducerContext = struct { + allocator: std.mem.Allocator, sender: spacetime.Identity, timestamp: spacetime.Timestamp, connection_id: spacetime.ConnectionId, @@ -648,7 +673,7 @@ pub const RawMiscModuleExportV9 = enum { RESERVED, }; -pub const RawSql = []u8; +pub const RawSql = Str; pub const RawRowLevelSecurityDefV9 = struct { sql: RawSql,