Reduce latency of itemdrops by sending item drop updates instantly instead of waiting for the next server tick.

progress towards #868
This commit is contained in:
IntegratedQuantum 2025-01-03 23:38:23 +01:00
parent 2202cd01da
commit 850956b0af
2 changed files with 80 additions and 66 deletions

View File

@ -62,13 +62,10 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
size: u32 = 0, size: u32 = 0,
lastUpdates: ZonElement,
pub fn init(self: *ItemDropManager, allocator: NeverFailingAllocator, world: ?*ServerWorld, gravity: f64) void { pub fn init(self: *ItemDropManager, allocator: NeverFailingAllocator, world: ?*ServerWorld, gravity: f64) void {
self.* = ItemDropManager { self.* = ItemDropManager {
.allocator = allocator, .allocator = allocator,
.list = std.MultiArrayList(ItemDrop){}, .list = std.MultiArrayList(ItemDrop){},
.lastUpdates = ZonElement.initArray(allocator),
.isEmpty = .initFull(), .isEmpty = .initFull(),
.changeQueue = .init(allocator, 16), .changeQueue = .init(allocator, 16),
.world = world, .world = world,
@ -87,7 +84,6 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
} }
} }
self.list.deinit(self.allocator.allocator); self.list.deinit(self.allocator.allocator);
self.lastUpdates.deinit(self.allocator);
} }
pub fn loadFrom(self: *ItemDropManager, zon: ZonElement) void { pub fn loadFrom(self: *ItemDropManager, zon: ZonElement) void {
@ -136,18 +132,18 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
} }
pub fn getInitialList(self: *ItemDropManager, allocator: NeverFailingAllocator) ZonElement { pub fn getInitialList(self: *ItemDropManager, allocator: NeverFailingAllocator) ZonElement {
self.processChanges(); // Make sure all the items from the queue are included.
var list = ZonElement.initArray(allocator); var list = ZonElement.initArray(allocator);
var ii: u32 = 0; var ii: u32 = 0;
while(ii < self.size) : (ii += 1) { while(ii < self.size) : (ii += 1) {
const i = self.indices[ii]; const i = self.indices[ii];
list.array.append(self.storeSingle(self.lastUpdates.array.allocator, i)); list.array.append(self.storeSingle(allocator, i));
} }
return list; return list;
} }
fn storeSingle(self: *ItemDropManager, allocator: NeverFailingAllocator, i: u16) ZonElement { fn storeDrop(allocator: NeverFailingAllocator, itemDrop: ItemDrop, i: u16) ZonElement {
const obj = ZonElement.initObject(allocator); const obj = ZonElement.initObject(allocator);
const itemDrop = self.list.get(i);
obj.put("i", i); obj.put("i", i);
obj.put("pos", itemDrop.pos); obj.put("pos", itemDrop.pos);
obj.put("vel", itemDrop.vel); obj.put("vel", itemDrop.vel);
@ -156,6 +152,10 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
return obj; return obj;
} }
fn storeSingle(self: *ItemDropManager, allocator: NeverFailingAllocator, i: u16) ZonElement {
return storeDrop(allocator, self.list.get(i), i);
}
pub fn store(self: *ItemDropManager, allocator: NeverFailingAllocator) ZonElement { pub fn store(self: *ItemDropManager, allocator: NeverFailingAllocator) ZonElement {
const zonArray = ZonElement.initArray(allocator); const zonArray = ZonElement.initArray(allocator);
for(self.indices[0..self.size]) |i| { for(self.indices[0..self.size]) |i| {
@ -187,10 +187,7 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
pickupCooldown[i] -= 1; pickupCooldown[i] -= 1;
despawnTime[i] -= 1; despawnTime[i] -= 1;
if(despawnTime[i] < 0) { if(despawnTime[i] < 0) {
self.emptyMutex.lock(); self.directRemove(i);
self.isEmpty.set(i);
self.emptyMutex.unlock();
self.internalRemove(i);
} else { } else {
ii += 1; ii += 1;
} }
@ -212,8 +209,7 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
return; return;
}); });
self.isEmpty.unset(i); self.isEmpty.unset(i);
self.emptyMutex.unlock(); const drop = ItemDrop {
self.changeQueue.enqueue(.{.add = .{i, .{
.pos = pos, .pos = pos,
.vel = vel, .vel = vel,
.rot = rot, .rot = rot,
@ -221,15 +217,32 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
.despawnTime = despawnTime, .despawnTime = despawnTime,
.pickupCooldown = pickupCooldown, .pickupCooldown = pickupCooldown,
.reverseIndex = undefined, .reverseIndex = undefined,
}}}); };
if(self.world != null) {
const list = ZonElement.initArray(main.stackAllocator);
defer list.deinit(main.stackAllocator);
list.array.append(.null);
list.array.append(storeDrop(main.stackAllocator, drop, i));
const updateData = list.toStringEfficient(main.stackAllocator, &.{});
defer main.stackAllocator.free(updateData);
const userList = main.server.getUserListAndIncreaseRefCount(main.stackAllocator);
defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
for(userList) |user| {
main.network.Protocols.entity.send(user.conn, updateData);
}
}
self.emptyMutex.unlock();
self.changeQueue.enqueue(.{.add = .{i, drop}});
} }
fn addWithIndex(self: *ItemDropManager, i: u16, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32) void { fn addWithIndex(self: *ItemDropManager, i: u16, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32) void {
self.emptyMutex.lock(); self.emptyMutex.lock();
std.debug.assert(self.isEmpty.isSet(i)); std.debug.assert(self.isEmpty.isSet(i));
self.isEmpty.unset(i); self.isEmpty.unset(i);
self.emptyMutex.unlock(); const drop = ItemDrop {
self.changeQueue.enqueue(.{.add = .{i, .{
.pos = pos, .pos = pos,
.vel = vel, .vel = vel,
.rot = rot, .rot = rot,
@ -237,7 +250,24 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
.despawnTime = despawnTime, .despawnTime = despawnTime,
.pickupCooldown = pickupCooldown, .pickupCooldown = pickupCooldown,
.reverseIndex = undefined, .reverseIndex = undefined,
}}}); };
if(self.world != null) {
const list = ZonElement.initArray(main.stackAllocator);
defer list.deinit(main.stackAllocator);
list.array.append(.null);
list.array.append(storeDrop(main.stackAllocator, drop, i));
const updateData = list.toStringEfficient(main.stackAllocator, &.{});
defer main.stackAllocator.free(updateData);
const userList = main.server.getUserListAndIncreaseRefCount(main.stackAllocator);
defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
for(userList) |user| {
main.network.Protocols.entity.send(user.conn, updateData);
}
}
self.emptyMutex.unlock();
self.changeQueue.enqueue(.{.add = .{i, drop}});
} }
fn processChanges(self: *ItemDropManager) void { fn processChanges(self: *ItemDropManager) void {
@ -260,9 +290,6 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
} }
drop.reverseIndex = @intCast(self.size); drop.reverseIndex = @intCast(self.size);
self.list.set(i, drop); self.list.set(i, drop);
if(self.world != null) {
self.lastUpdates.array.append(self.storeSingle(self.lastUpdates.array.allocator, i));
}
self.indices[self.size] = i; self.indices[self.size] = i;
self.size += 1; self.size += 1;
} }
@ -273,9 +300,28 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
self.list.items(.itemStack)[i].clear(); self.list.items(.itemStack)[i].clear();
self.indices[ii] = self.indices[self.size]; self.indices[ii] = self.indices[self.size];
self.list.items(.reverseIndex)[self.indices[self.size]] = ii; self.list.items(.reverseIndex)[self.indices[self.size]] = ii;
if(self.world != null) { }
self.lastUpdates.array.append(.{.int = i});
fn directRemove(self: *ItemDropManager, i: u16) void {
std.debug.assert(self.world != null);
self.emptyMutex.lock();
self.isEmpty.set(i);
const list = ZonElement.initArray(main.stackAllocator);
defer list.deinit(main.stackAllocator);
list.array.append(.null);
list.array.append(.{.int = i});
const updateData = list.toStringEfficient(main.stackAllocator, &.{});
defer main.stackAllocator.free(updateData);
const userList = main.server.getUserListAndIncreaseRefCount(main.stackAllocator);
defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
for(userList) |user| {
main.network.Protocols.entity.send(user.conn, updateData);
} }
self.emptyMutex.unlock();
self.internalRemove(i);
} }
fn updateEnt(self: *ItemDropManager, chunk: *ServerChunk, pos: *Vec3d, vel: *Vec3d, deltaTime: f64) void { fn updateEnt(self: *ItemDropManager, chunk: *ServerChunk, pos: *Vec3d, vel: *Vec3d, deltaTime: f64) void {
@ -374,10 +420,7 @@ pub const ItemDropManager = struct { // MARK: ItemDropManager
const itemStack = &self.list.items(.itemStack)[i]; const itemStack = &self.list.items(.itemStack)[i];
main.items.Inventory.Sync.ServerSide.tryCollectingToPlayerInventory(user, itemStack); main.items.Inventory.Sync.ServerSide.tryCollectingToPlayerInventory(user, itemStack);
if(itemStack.amount == 0) { if(itemStack.amount == 0) {
self.emptyMutex.lock(); self.directRemove(i);
self.isEmpty.set(i);
self.emptyMutex.unlock();
self.internalRemove(i);
continue; continue;
} }
} }

View File

@ -296,44 +296,18 @@ pub fn freeUserListAndDecreaseRefCount(allocator: utils.NeverFailingAllocator, l
allocator.free(list); allocator.free(list);
} }
fn sendEntityUpdates(comptime getInitialList: bool, allocator: utils.NeverFailingAllocator) if(getInitialList) []const u8 else void { fn getInitialEntityList(allocator: utils.NeverFailingAllocator) []const u8 {
// Send the entity updates: // Send the entity updates:
const updateList = main.ZonElement.initArray(main.stackAllocator);
defer updateList.deinit(main.stackAllocator);
defer updateList.array.clearAndFree(); // The children are freed in other locations.
if(world.?.itemDropManager.lastUpdates.array.items.len != 0) {
updateList.array.append(.null);
updateList.array.appendSlice(world.?.itemDropManager.lastUpdates.array.items);
}
if(!getInitialList and updateList.array.items.len == 0) {
return;
}
const updateData = updateList.toStringEfficient(main.stackAllocator, &.{});
defer main.stackAllocator.free(updateData);
if(world.?.itemDropManager.lastUpdates.array.items.len != 0) {
const alloc = world.?.itemDropManager.lastUpdates.array.allocator;
world.?.itemDropManager.lastUpdates.deinit(alloc);
world.?.itemDropManager.lastUpdates = main.ZonElement.initArray(alloc);
}
var initialList: []const u8 = undefined; var initialList: []const u8 = undefined;
if(getInitialList) { const list = main.ZonElement.initArray(main.stackAllocator);
const list = main.ZonElement.initArray(main.stackAllocator); defer list.deinit(main.stackAllocator);
defer list.deinit(main.stackAllocator); list.array.append(.null);
list.array.append(.null); const itemDropList = world.?.itemDropManager.getInitialList(main.stackAllocator);
const itemDropList = world.?.itemDropManager.getInitialList(main.stackAllocator); list.array.appendSlice(itemDropList.array.items);
list.array.appendSlice(itemDropList.array.items); itemDropList.array.items.len = 0;
itemDropList.array.items.len = 0; itemDropList.deinit(main.stackAllocator);
itemDropList.deinit(main.stackAllocator); initialList = list.toStringEfficient(allocator, &.{});
initialList = list.toStringEfficient(allocator, &.{}); return initialList;
}
const userList = getUserListAndIncreaseRefCount(main.stackAllocator);
defer freeUserListAndDecreaseRefCount(main.stackAllocator, userList);
for(userList) |user| {
main.network.Protocols.entity.send(user.conn, updateData);
}
if(getInitialList) {
return initialList;
}
} }
fn update() void { // MARK: update() fn update() void { // MARK: update()
@ -349,9 +323,6 @@ fn update() void { // MARK: update()
user.update(); user.update();
} }
sendEntityUpdates(false, main.stackAllocator);
// Send the entity data: // Send the entity data:
const data = main.stackAllocator.alloc(u8, (4 + 24 + 12 + 24)*userList.len); const data = main.stackAllocator.alloc(u8, (4 + 24 + 12 + 24)*userList.len);
defer main.stackAllocator.free(data); defer main.stackAllocator.free(data);
@ -482,7 +453,7 @@ pub fn connectInternal(user: *User) void {
defer main.stackAllocator.free(data); defer main.stackAllocator.free(data);
if(user.connected.load(.unordered)) main.network.Protocols.entity.send(user.conn, data); if(user.connected.load(.unordered)) main.network.Protocols.entity.send(user.conn, data);
} }
const initialList = sendEntityUpdates(true, main.stackAllocator); const initialList = getInitialEntityList(main.stackAllocator);
main.network.Protocols.entity.send(user.conn, initialList); main.network.Protocols.entity.send(user.conn, initialList);
main.stackAllocator.free(initialList); main.stackAllocator.free(initialList);
const message = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}§#ffff00 joined", .{user.name}) catch unreachable; const message = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}§#ffff00 joined", .{user.name}) catch unreachable;