Stop panic when blueprint doesn't exist for biome#2195
Stop panic when blueprint doesn't exist for biome#2195IntegratedQuantum merged 16 commits intoPixelGuys:masterfrom
Conversation
…and print error message
| }; | ||
| const VTable = struct { | ||
| loadModel: *const fn(parameters: ZonElement) *anyopaque, | ||
| loadModel: *const fn(parameters: ZonElement) ?*anyopaque, |
There was a problem hiding this comment.
Although compiler seems to ignore it, I think it is unsafe to use ?*anyopaque here without changing the return type in all structure implementation. Current implementation can support it, but its definitely not the part of the contract. Even if it would be completely safe, for sake of consistency return type in every implementation of loadModel should be optional.
There was a problem hiding this comment.
Could you then please also adjust the corresponding casting functions (utils.castFunctionReturnToAnyopaque and utils.castFunctionSelfToAnyopaque) so they do throw a compiler error on optional mismatch.
There was a problem hiding this comment.
I'm not entirely sure what you mean by optional mismatch, but I'll give it my best shot.
There was a problem hiding this comment.
I think I'm piecing it together.
You want a compiler error thrown if something is added that implements loadModel but it tries to return a non-optional anyopaque
There was a problem hiding this comment.
On second thought there's more nuance to that.
It needs to know whether to expect an optional anyopaque or just an anyopaque. I'm not sure how it can know that, yet.
There was a problem hiding this comment.
The simplest solution seems to be to create a new function for optionals.
I can't seem to figure out a cleaner way that keeps everything in one generalized function.
There was a problem hiding this comment.
Okay I pushed up an attempt at this. It feels wrong, though.
… message is logged
| pub fn loadModel(parameters: ZonElement) *SbbGen { | ||
| pub fn loadModel(parameters: ZonElement) ?*SbbGen { | ||
| const structureId = parameters.get(?[]const u8, "structure", null) orelse { | ||
| main.utils.panicWithMessage("Error loading generator 'cubyz:sbb' structure field is mandatory.", .{}); |
There was a problem hiding this comment.
Please also fix this while you are at it.
There was a problem hiding this comment.
Done, this should now fix #1932 too.
I think my implementation might be a little ugly but I can clean that up later.
The root cause of the issue is that we resize structureList to a size larger than it would be if some sbbs are discarded.
Then when we loop over it, the last n items of the slice are invalid memory. (address aaaaaaaaa... and so on)
My fix is to bound the slice we loop over by how many items made it through registration. (0..stage1Count) Naming can be adjusted.
There was a problem hiding this comment.
I was thinking about this some more, and an ideal approach would resize the list to the new size.
I couldn't think of a good way to do this, the typical resize function doesn't allow you to make the list smaller.
I guess we could supply a new, smaller list with the 0..stage1Count slice to reassign to structureList? Unless there's some obvious way I'm missing to reduce list size
There was a problem hiding this comment.
Please read
Line 15 in 64b5813
Whole implementation
You will find solution to your problem there. A pretty one.
There was a problem hiding this comment.
Just so it's clear, none cares if we use the whole allocation or if we waste few entries of memory, only correctness matter.
If solution doesn't occur to you after reading List implementation I will gladly suggest a stage1Couny-free solution without further riddles.
There was a problem hiding this comment.
To be clear, it does not seem to add capacity to the list if we hit the catch on initFromZon. It cannot call the function to add capacity until the parameter is resolved.
There was a problem hiding this comment.
Yes, there is a bug in current implementation, at least after reviewing the code on phone I think I see it. Uninitialized objects should have been removed.
so you guarantee that your list doesn't use 2N storage due to resizes, if someone allocate something else in meantime
I'm not following. It'll only add capacity to the list when it adds a sbb during world load, which is discarded by the
worldArenaonce the world closes. Where's2Ncoming from here?
Exactly here. World arena exists throughout whole gameplay, as long as the world is open. Therefore you will keep those unused allocations for most of the runtime. When you start appending, a list will start with small size, eg. N/8, then after reaching it's capacity (mind, not size, capacity!) it will reallocate (not resize!) to N/4 and repeat the cycle until it reaches N + possibly some overshoot due to X2 growth. Under certain circumstances allocation will not be resized (ie. won't be extended without changing address) but will be changed to completely different place, hence leaking the memory until world arena is reset.
Again, there is a difference between size and capacity, allocation size and array size.
There was a problem hiding this comment.
Therefore you will keep those unused allocations for most of the runtime.
But my implementation avoids the unused allocations altogether?
It won't run .append if .initFromZon throws an error. It resolves the parameters before it ever calls the function.
There was a problem hiding this comment.
Measured via the arenaAllocator.queryCapacity function:
My branch:
worldArena capacity before sbbs: 2950248 (usize)
worldArena capacity after sbbs: 2950248 (usize)
master:
worldArena capacity before sbbs: 2950492 (usize)
worldArena capacity after sbbs: 2950492 (usize)
There was a problem hiding this comment.
Ok, remove line with preallocation of hashmap memory and try checking size of the arena again.
structureMap.ensureTotalCapacity(main.worldArena.allocator, structures.count()) catch unreachable;
I am not sure it will expose the problem, because I am not sure what queryCapacity actually returns, but maybe it will be visible.
Regardless of the outcome, it's not about current state of implementation, is about the semantics and potential future bugs.
When you call append on a list, it doesn't change it's allocation size by exactly one. It allocates continuous array of capacity of X elements but exposes size of 1. Then, you can freely append to it without reallocating until you try to append X+1 element. Then, list requests a resize (reallocation) of it's allocation to 2*X size. Allocator can decide to either grow that allocation, if it has unused space behind it, or has to create new memory region, copy old allocation into it and return a new pointer. Regardless of the path, list will expose size of X+1, but will have capacity of 2*X. This will be repeated until list can contain all N elements. In case of Arena allocator, second reallocation path will result in old allocation being unused, but still owned by the arena.
src/utils.zig
Outdated
| typeInfo.@"fn".params = params[0..]; | ||
| return @Type(typeInfo); | ||
| } | ||
| fn CastFunctionSelfToOptionalAnyopaqueType(Fn: type) type { |
There was a problem hiding this comment.
What would an optional self parameter mean, an how would it ever be useful?
Don't add functions that we don't need.
|
|
||
| fn CastFunctionReturnToOptionalAnyopaqueType(Fn: type) type { | ||
| var typeInfo = @typeInfo(Fn); | ||
| if(@sizeOf(typeInfo.@"fn".return_type.?) != @sizeOf(?*anyopaque) or @alignOf(typeInfo.@"fn".return_type.?) != @alignOf(?*anyopaque) or @typeInfo(typeInfo.@"fn".return_type.?) != .optional) { |
There was a problem hiding this comment.
Please also adjust the non-optional function to do the inverse check.
| std.debug.assert(structureList.items.len == 0); | ||
| std.debug.assert(structureMap.capacity() == 0); | ||
|
|
||
| structureList.resize(main.worldArena, structures.count()); |
There was a problem hiding this comment.
Please add ensureCapacity call with structures.count() to ensure structureList never has to reallocate it's storage.
I just have it returning null if it can't find the blueprint and printing an error in the log.
It should work the same as if the id was not found.
Fixes #1898
Fixes #1932