-
Notifications
You must be signed in to change notification settings - Fork 188
Introduce new Sync command CraftFrom and make the crafting inventories client side only
#2506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Ok, so I am getting a bit confused by the description. What exactly is getting client sided here? Recipe resolution right? Not whole item transformation? |
For reference see: #2500 . There Quantum introduced the ClientInventory this can either be synced with a Normal inventory on the server or can be completly be handeld on client side. This PR introduces the CraftFrom sync so that the server doesn't have to have access to the crafting inventories (which are displayed in the crafting menu). Which makes it possible to make them also only client side as is the creative menu through #2500 |
|
Aight, since you are clearly keeping up with the topic, could you please answer for sake of documentation: Can the client invoke arbitrary item transformation that was not defined by server side recipe table? Will the server reject such transformation? |
src/sync.zig
Outdated
| // check if recipe is valid | ||
| const found = blk: { | ||
| outer: for(main.items.recipes()) |*recipe| { | ||
| if(recipe.resultItem != resultStack.item.baseItem) continue; | ||
| if(recipe.resultAmount != resultStack.amount) continue; | ||
| if(recipe.sourceItems.len != sourceStacksSize) continue; | ||
| for(recipe.sourceItems, recipe.sourceAmounts, sourceStacks) |recipeItem, recipeAmount, sourceStack| { | ||
| if(recipeItem != sourceStack.item.baseItem) continue :outer; | ||
| if(recipeAmount != sourceStack.amount) continue :outer; | ||
| } | ||
| break :blk true; | ||
| } | ||
| break :blk false; | ||
| }; | ||
| if(!found) return error.Invalid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks if the recipe actually exist, but I have not yet tested if there is a flaw in my logic here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries, I am just trying to understand the intentions
src/sync.zig
Outdated
| } | ||
|
|
||
| fn deserialize(reader: *BinaryReader, side: Side, user: ?*main.server.User) !CraftFrom { | ||
| const destinationsSize = try reader.readVarInt(usize); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, varInt. (This comment mainly to addressed @IntegratedQuantum) How often is this message send so this varInt makes sense here? It's saving 6? 7? Bytes. Out of how large impact over eg. Hour of average gameplay? Anyway, my skepticism aside, shouldn't we make the enums varInts too? Since we care about those few bytes, I am almost sure we are wasting double that when all the entries are summed up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw this is destinationCount (possibly with plural destinations), size has to strong size-in-bytes connotations in this context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, let me point you to the contributing guidelines, using varint is non-pessimization. Of course in this case it makes no difference, but if we save a few bytes in each packet, then suddenly we are looking at different dimensions.
And there is no reason not to use varint, it's just 3 characters more to write.
You are also correct about the enums here, since they (and all non-exhaustive enums) are indices, it would make sense to serialize them as varints as well, and this might actually make a meaningful enough difference for item drop/entitiy/projectile ids. So maybe we should think about automatically doing this in the background.
src/sync.zig
Outdated
| for(self.sources) |source| if(source.type != .normal) return; | ||
|
|
||
| // Can we even craft it? | ||
| outer: for(self.sourceStacks) |requiredStack| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect we have to wait with improvements for next PR?
This cries for a helper function. Abstraction level in here is too low and the function is too long overall.
This is clearly a separate function that would be better off with lower indentation level and a proper name, rather than "outer" label.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ye without a proper abstraction level adjustment I am not reviewing that, but I know a person who will.
src/sync.zig
Outdated
| const sourceStacksSize = try reader.readVarInt(usize); | ||
| if(sourceStacksSize == 0) return error.Invalid; | ||
| std.log.info("sourceStacksSize: {d}, sourceStacksSize*@sizeOf(ItemStack): {d}, remaining: {d}\n", .{sourceStacksSize, sourceStacksSize*@sizeOf(ItemStack), reader.remaining.len}); | ||
| // if(sourceStacksSize*@sizeOf(ItemStack) > reader.remaining.len) return error.Invalid; // this check sadly fails |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Umm >=?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When writting the code first I changed to > because this is the last read so if its maybe = then this would still be ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway my suggestion is to read until it fails and hard cap max count we can process, as described In nearby comment.
src/sync.zig
Outdated
| } | ||
| } | ||
|
|
||
| fn deserialize(reader: *BinaryReader, side: Side, user: ?*main.server.User) !CraftFrom { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could unit test this fairly easily. We would have to be naught and add nvm we can't easily make a dymmy CraftFrom object.mock entry to Side enums that result in getInventory being noop and reserve a sentinel InventoryId, but we could get serialization edge case coverage from that. Alternatively we can just implement declarative serialization C:
src/sync.zig
Outdated
| fn deserialize(reader: *BinaryReader, side: Side, user: ?*main.server.User) !CraftFrom { | ||
| const destinationsSize = try reader.readVarInt(usize); | ||
| if(destinationsSize == 0) return error.Invalid; | ||
| if(destinationsSize*@sizeOf(InventoryId) >= reader.remaining.len) return error.Invalid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have to check this ahead of time? We can just for loop for destinationCount times and try reading. Reader should report error when it runs out of buffer data I think? We then catch it and possibly transform to Invalid or whatever.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, then I guess it's bad I missed that one. Suggestion still remains valid IMO, unless proven otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tho I admit that IMO a best solution would be an automagical serialization for structs, but Quantum disagrees.
src/sync.zig
Outdated
| sourceStack.* = try ItemStack.fromBytes(reader); | ||
| } | ||
|
|
||
| // check if recipe is valid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very pretty comment. Now make it a function name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved
This is extracted from: #2506 (comment) Initially this was only supposed to be a helper function, but as I wanted to improve the abstraction it grew into a struct to avoid namespace pollution. This will also be helpful if #2506 is not accepted, as I have at least one other slice refactor that currently copies this code (#2494), and we may extend additional functions to slices or make more operations use the same priority system.
|
|
this is now updated to use #2524. |
|
Hmm, I'm not sure. I think for now it would be better to have one clear pattern: Check if the reader has enough data left before allocating. |
IntegratedQuantum
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also add the check to fromBytes please
|
|
||
| // Can we even craft it? | ||
| for (self.recipe.sourceItems, self.recipe.sourceAmounts) |requiredItem, requiredAmount| { | ||
| self.sources.removeItems(ctx, requiredAmount, requiredItem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please first check if we have the needed ingredients, before removing anything (you accidentally removed that code)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readded. It seems I removed it when introduced the remove_items namespace...
Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
|
So now we go back to the problem I described at the beginning of the PR. The check for recipe |
src/items.zig
Outdated
| const resultAmount = try reader.readVarInt(u16); | ||
| const sourceCount = try reader.readVarInt(usize); | ||
| if (sourceCount == 0) return error.Invalid; | ||
| if (sourceCount*@sizeOf(Recipe) > reader.remaining.len) return error.Invalid; // this check fails |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be @sizeOf(u16) + @sizeOf(BaseItemIndex), but even that doesn't account for the compression of the varint.
So instead, I'd suggest to just use a pair of Lists, that are maybe preallocated to @min(256, sourceCount).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understood correctly what you wanted. Its now there. (but please no merge It seems I removed a check so now if you click a sourceitem the game crashes)
src/sync.zig
Outdated
| // There might be duplicate entries: | ||
| for (self.recipe.sourceItems, self.recipe.sourceAmounts) |otherItem, otherAmount| { | ||
| if (std.meta.eql(requiredItem, otherItem)) | ||
| amount += otherAmount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
|
check is revived. |
src/gui/gui.zig
Outdated
|
|
||
| if (itemSlot.inventory.type == .crafting and itemSlot.mode == .takeOnly and mainGuiButton.pressed and (recipeItem != .null or itemSlot.pressed)) { | ||
| if (itemSlot.inventory.type == .crafting and mainGuiButton.pressed and (recipeItem != .null or itemSlot.pressed)) { | ||
| if (itemSlot.mode != .takeOnly) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you explain this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actual thought this was also a case where I needed to add the check again. But yeah... it also works without this change so I reverted that.
|
I'm also still wondering about the other change from that "check is revived" part, why did it need to change? |
When you click the left "source" inventories of the recipe menu you are in a recipe inventory but its not |
IntegratedQuantum
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a few more things
src/items.zig
Outdated
| const resultItem = try reader.readEnum(BaseItemIndex); | ||
| const resultAmount = try reader.readVarInt(u16); | ||
| const sourceCount = try reader.readVarInt(usize); | ||
| if (sourceCount == 0) return error.Invalid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't dismiss this so quickly. The rest of the engine allows it, and there is no reason not to allow addon creators to define recipes like this. This might in fact make more sense once tool durability and special crafting stations are added to the mix.
src/items.zig
Outdated
| var sourceAmounts: main.List(u16) = .initCapacity(main.stackAllocator, @min(256, sourceCount)); | ||
| defer sourceAmounts.deinit(); | ||
|
|
||
| while (reader.remaining.len > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should add most add sourceCount items. Otherwise we may cause problems if there is other stuff afterwards that expects to read things from the same reader.
Co-authored-by: IntegratedQuantum <43880493+IntegratedQuantum@users.noreply.github.com>
IntegratedQuantum
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for working on this, I think it's a great improvement both in terms of readability and feature consistency.
If you are up to it, the next step in that direction would be #2529, it would be amazing if we could get rid of all the edge cases that came with the server-shared inventory type.
maybe. I will have to read into the tool logic a little bit before to understand whats exactly going on there. |
Extracted from: #2500 (comment)
Fixes: #2497
deserialize(As already mentioned in DepositToAny slice for the destination #2469 (comment) I am not that knowledgable on the reader code so if anybody has a better idea here, it would be very welcome)This PR also replaces #2483 as its features are also done here.
CraftFromaccepts as source a slice to prepare for #2324 or/and making the user able to craft from items in a chest