Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 53 additions & 24 deletions src/Inventory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1670,16 +1670,21 @@ pub const Command = struct { // MARK: Command
};

const DepositToAny = struct { // MARK: DepositToAny
dest: Inventory,
destinations: []Inventory,
owned: bool = false,
source: InventoryAndSlot,
amount: u16,

fn run(self: DepositToAny, ctx: Context) error{serverFailure}!void {
if(self.dest.type == .creative) return;
if(self.dest.type == .crafting) return;
if(self.dest.type == .workbench) return;
defer if(self.owned) main.stackAllocator.free(self.destinations);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

command may run multiple times on the client side, freeing here causes a double free. Instead you can free this in finalize.
Also the stackAllocator is a poor choice, only use the stackAllocator for local allocations, this allocation persists for multiple threads and likely will be ran from a different thread as well.

Also the allocator shouldn't be hardcoded here, instead the caller should pass it.

Lastly who even owns the memory in the other case? How do you keep track of something that gets used possible many frames after passing it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

command may run multiple times on the client side, freeing here causes a double free. Instead you can free this in finalize. Also the stackAllocator is a poor choice, only use the stackAllocator for local allocations, this allocation persists for multiple threads and likely will be ran from a different thread as well.

done changed to the globalAllocator which is used for other payloads

Also the allocator shouldn't be hardcoded here, instead the caller should pass it.

so should I change the finalize to parse over the globalAllocator? That looks better to me at least.

Lastly who even owns the memory in the other case? How do you keep track of something that gets used possible many frames after passing it?

Yeah you are right there... I will look into how I want to solve that. I could maybe just dupe the slice so that its always owned? But I don't want to that in gui.zig where depositToAny is called... I will sleep over it and look tomorrow at the code again.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so should I change the finalize to parse over the globalAllocator? That looks better to me at least.

No, it should be passed on creation, since the finalize call is already really disconnected from the creation.
This would prevent accidentally using mismatched allocators.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switched the owned variable with an allocator. Also did the other issue with an dupe so it should now always be owned.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also consider making a separate .init function that allocates everything inside. Then I would be fine with not having the allocator stored since it's all used locally.

if(self.destinations.len == 0) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would this even happen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A modified Client can send anything. I just want to put a check in before it later causes bugs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is about safety of client data, then you should put this check in the deserialization function.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client data should generally be sanitized during or immediately after receiving.

for(self.destinations) |dest| {
if(dest.type == .creative) return;
if(dest.type == .crafting) return;
if(dest.type == .workbench) return;
}
if(self.source.inv.type == .crafting) {
ctx.cmd.tryCraftingTo(ctx.allocator, self.dest, self.source, ctx.side, ctx.user);
ctx.cmd.tryCraftingTo(ctx.allocator, self.destinations[0], self.source, ctx.side, ctx.user);
return;
}
const sourceStack = self.source.ref();
Expand All @@ -1688,41 +1693,65 @@ pub const Command = struct { // MARK: Command

var remainingAmount = self.amount;
var selectedEmptySlot: ?u32 = null;
for(self.dest._items, 0..) |*destStack, destSlot| {
if(destStack.item == .null and selectedEmptySlot == null) {
selectedEmptySlot = @intCast(destSlot);
var selectedEmptyInv: u8 = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why limit this to a u8? It should be usize, there is nothing gained by using a u8, other than future bugs from the intCast.

var selectedEmptyInvHasItem = false;
outer: for(self.destinations, 0..) |dest, destInv| {
var emptySlot: ?u32 = null;
var hasItem = false;
for(dest._items, 0..) |*destStack, destSlot| {
if(destStack.item == .null and emptySlot == null) {
emptySlot = @intCast(destSlot);
}
if(std.meta.eql(destStack.item, sourceStack.item)) {
hasItem = true;
const amount = @min(sourceStack.item.stackSize() - destStack.amount, remainingAmount);
if(amount == 0) continue;
ctx.execute(.{.move = .{
.dest = .{.inv = dest, .slot = @intCast(destSlot)},
.source = self.source,
.amount = amount,
}});
remainingAmount -= amount;
if(remainingAmount == 0) break :outer;
}
}
if(std.meta.eql(destStack.item, sourceStack.item)) {
const amount = @min(sourceStack.item.stackSize() - destStack.amount, remainingAmount);
if(amount == 0) continue;
ctx.execute(.{.move = .{
.dest = .{.inv = self.dest, .slot = @intCast(destSlot)},
.source = self.source,
.amount = amount,
}});
remainingAmount -= amount;
if(remainingAmount == 0) break;
if(emptySlot != null and (selectedEmptySlot == null or (hasItem and !selectedEmptyInvHasItem))) {
selectedEmptySlot = emptySlot;
selectedEmptyInv = @intCast(destInv);
selectedEmptyInvHasItem = hasItem;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is different from the behavior suggested in #2324 (review), here you seem to be first topping off partially filled stacks in the other inventories.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all done

}
}
if(remainingAmount > 0 and selectedEmptySlot != null) {
ctx.execute(.{.move = .{
.dest = .{.inv = self.dest, .slot = selectedEmptySlot.?},
.dest = .{.inv = self.destinations[selectedEmptyInv], .slot = selectedEmptySlot.?},
.source = self.source,
.amount = remainingAmount,
}});
}
}

fn serialize(self: DepositToAny, writer: *utils.BinaryWriter) void {
writer.writeEnum(InventoryId, self.dest.id);
writer.writeInt(u8, @intCast(self.destinations.len));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a varint and send a usize
Putting an arbitrary limit on the number and crashing when exceeded is not helpful.
If you want to limit this then you should have a good reason for it, and 256 might definitely be in reach for a deposit to nearby chests function.

for(self.destinations) |dest| {
writer.writeEnum(InventoryId, dest.id);
}
self.source.write(writer);
writer.writeInt(u16, self.amount);
}

fn deserialize(reader: *utils.BinaryReader, side: Side, user: ?*main.server.User) !DepositToAny {
const destId = try reader.readEnum(InventoryId);
const destinationsSize = try reader.readInt(u8);
var destinations = main.stackAllocator.alloc(Inventory, destinationsSize);
errdefer main.stackAllocator.free(destinations);

for(destinations) |*dest| {
const invId = try reader.readEnum(InventoryId);
dest.* = Sync.getInventory(invId, side, user) orelse return error.InventoryNotFound;
}

return .{
.dest = Sync.getInventory(destId, side, user) orelse return error.InventoryNotFound,
.destinations = destinations[0..],
.owned = true,
.source = try InventoryAndSlot.read(reader, side, user),
.amount = try reader.readInt(u16),
};
Expand Down Expand Up @@ -2178,8 +2207,8 @@ pub fn depositOrDrop(dest: Inventory, source: Inventory) void {
Sync.ClientSide.executeCommand(.{.depositOrDrop = .{.dest = dest, .source = source, .dropLocation = undefined}});
}

pub fn depositToAny(source: Inventory, sourceSlot: u32, dest: Inventory, amount: u16) void {
Sync.ClientSide.executeCommand(.{.depositToAny = .{.dest = dest, .source = .{.inv = source, .slot = sourceSlot}, .amount = amount}});
pub fn depositToAny(source: Inventory, sourceSlot: u32, destinations: []Inventory, amount: u16) void {
Sync.ClientSide.executeCommand(.{.depositToAny = .{.destinations = destinations, .source = .{.inv = source, .slot = sourceSlot}, .amount = amount}});
}

pub fn dropStack(source: Inventory, sourceSlot: u32) void {
Expand Down
9 changes: 6 additions & 3 deletions src/gui/gui.zig
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,8 @@ pub const inventory = struct { // MARK: inventory
nextCraftingAction = nextCraftingAction.addDuration(craftingCooldown);
craftingCooldown.nanoseconds -= @divTrunc((craftingCooldown.nanoseconds -% minCraftingCooldown.nanoseconds)*craftingCooldown.nanoseconds, std.time.ns_per_s);
if(mainGuiButton.modsOnPress.shift) {
itemSlot.inventory.depositToAny(itemSlot.itemSlot, main.game.Player.inventory, itemSlot.inventory.getAmount(itemSlot.itemSlot));
var destinations = [_]Inventory{main.game.Player.inventory};
itemSlot.inventory.depositToAny(itemSlot.itemSlot, destinations[0..], itemSlot.inventory.getAmount(itemSlot.itemSlot));
} else {
itemSlot.inventory.depositOrSwap(itemSlot.itemSlot, carried);
}
Expand All @@ -683,12 +684,14 @@ pub const inventory = struct { // MARK: inventory
var iterator = std.mem.reverseIterator(openWindows.items);
while(iterator.next()) |window| {
if(window.shiftClickableInventory) |inv| {
itemSlot.inventory.depositToAny(itemSlot.itemSlot, inv, itemSlot.inventory.getAmount(itemSlot.itemSlot));
var destinations = [_]Inventory{inv};
itemSlot.inventory.depositToAny(itemSlot.itemSlot, destinations[0..], itemSlot.inventory.getAmount(itemSlot.itemSlot));
break;
}
}
} else {
itemSlot.inventory.depositToAny(itemSlot.itemSlot, main.game.Player.inventory, itemSlot.inventory.getAmount(itemSlot.itemSlot));
var destinations = [_]Inventory{main.game.Player.inventory};
itemSlot.inventory.depositToAny(itemSlot.itemSlot, destinations[0..], itemSlot.inventory.getAmount(itemSlot.itemSlot));
}
return;
}
Expand Down