Page 1 of 2

Saving/Loading big map table

Posted: Mon May 21, 2018 10:08 pm
by LXDominik
Hi everyone!
I'm trying to save generated map in a file, then load it.
If map is small like 128x128 i have no problem saving/loading it using bitser lib. But if map is 8192x2048 i can't load it anymore, application just closes printing this to console '[Finished in 1.8s with exit code 3221225477]'.

Also I'm doing map generation in love.load, and application is 'not responding' while map is generating, after map generation functions are done, application starts responding normally. Do i need to use something like coroutine for generation, so i can show progress bars and such? Or there is better ways to do this?

Re: Saving/Loading big map table

Posted: Mon May 21, 2018 10:16 pm
by zorg
coroutines are fine, but you'll need to yield in love.update, since love.load only gets called once at startup.
otherwise you could use actual threads, but that's more complicated.

as for the bitser thing, it may or may not have a bug with large amounts of data...

Re: Saving/Loading big map table

Posted: Mon May 21, 2018 11:02 pm
by LXDominik
zorg wrote: Mon May 21, 2018 10:16 pm coroutines are fine, but you'll need to yield in love.update, since love.load only gets called once at startup.
otherwise you could use actual threads, but that's more complicated.

as for the bitser thing, it may or may not have a bug with large amounts of data...
i've tried all serializers listed on love2d wiki, bitser was the only one that managed to save such big table.
do you have any suggestions what should i try?

Re: Saving/Loading big map table

Posted: Tue May 22, 2018 3:34 am
by pgimeno
Try Smallfolk maybe? https://github.com/gvx/Smallfolk - it's not binary, though, so the size will be larger. But since it doesn't generate Lua source, it will probably be able to handle that table size.

Also consider the possibility that it's LuaJIT going out of memory. If that's the case, allocating through ffi.new instead of using tables would help. But you'll have to come up with your own serializer in that case.

Re: Saving/Loading big map table

Posted: Tue May 22, 2018 5:54 am
by KayleMaster
If bitser happens to silently crash the game with large amounts of data, you probably need to increase it's buffer size. That's what fixed it for me @zorg

Re: Saving/Loading big map table

Posted: Tue May 22, 2018 8:24 am
by grump
What exactly do you store in the map?

Shameless plug for Blob, my binary serialization library. (De-)serialization of 2048 * 8192 ints:

Code: Select all

local blob = Blob(nil, 2048 * 8192 * 4)
for y = 0, 2047 do
	for x = 1, 8192 do
		blob:writeU32(y * 8192 + x)
	end
end

local map = {}
for y = 0, 2047 do
	for x = 1, 8192 do
		map[y * 8192 + x] = blob:readU32()
	end
end
Doesn't crash, is fast enough to not stall (above code takes ~240 ms in a VM on my laptop) and stores the data in a compact format.

It does also serialize tables, but tables that large would have a hefty overhead; one byte overhead per table key, one byte per table value (except for booleans), and all numbers are stored as 64 bit values.

Re: Saving/Loading big map table

Posted: Tue May 22, 2018 12:24 pm
by LXDominik
grump wrote: Tue May 22, 2018 8:24 am What exactly do you store in the map?

Shameless plug for Blob, my binary serialization library. (De-)serialization of 2048 * 8192 ints:

Code: Select all

local blob = Blob(nil, 2048 * 8192 * 4)
for y = 0, 2047 do
	for x = 1, 8192 do
		blob:writeU32(y * 8192 + x)
	end
end

local map = {}
for y = 0, 2047 do
	for x = 1, 8192 do
		map[y * 8192 + x] = blob:readU32()
	end
end
Doesn't crash, is fast enough to not stall (above code takes ~240 ms in a VM on my laptop) and stores the data in a compact format.

It does also serialize tables, but tables that large would have a hefty overhead; one byte overhead per table key, one byte per table value (except for booleans), and all numbers are stored as 64 bit values.
In my map i store type of a block 'map[x][y].type = 0'
But how do i save to/load from file using this Blob lib?

Re: Saving/Loading big map table

Posted: Tue May 22, 2018 12:53 pm
by grump
LXDominik wrote: Tue May 22, 2018 12:24 pm In my map i store type of a block 'map[x][y].type = 0'
For optimal results, you should define a maximum value range for type, choose a matching data type (8 bits, 16 bits, 32 bits, 64 bits are possible) and use the corresponding Blob:write*/read* functions as shown above. I advise against serializing huge arrays with Blob:writeTable(), since it introduces overhead and is considerably slower than doing it manually.
But how do i save to/load from file using this Blob lib?
Saving and loading is beyond the scope of the lib. You can either use

Code: Select all

-- save
assert(love.filesystem.write("filename", blob:string()))

-- load
local blob = Blob(assert(love.filesystem.read("filename")))
or the Lua io functions (not suitable for .love files):

Code: Select all

-- save
local file = assert(io.open("filename", "wb"))
file:write(blob:string())
file:close()

-- load
local file = assert(io.open("filename", "rb"))
local blob = Blob(file:read("*all"))
file:close()
See also the examples folder in the Blob repo.

Edit: it's designed to work with data in RAM - if your maps don't fit at least twice into memory (once for the raw data, and at least the same amount of RAM for the parsed data), it'll become harder to serialize them, because you have to implement chunk loading yourself. 8192 * 2048 ints need 64 MB of RAM, so that should not be a huge problem for you.

Re: Saving/Loading big map table

Posted: Tue May 22, 2018 2:15 pm
by LXDominik
grump wrote: Tue May 22, 2018 12:53 pm
LXDominik wrote: Tue May 22, 2018 12:24 pm In my map i store type of a block 'map[x][y].type = 0'
For optimal results, you should define a maximum value range for type, choose a matching data type (8 bits, 16 bits, 32 bits, 64 bits are possible) and use the corresponding Blob:write*/read* functions as shown above. I advise against serializing huge arrays with Blob:writeTable(), since it introduces overhead and is considerably slower than doing it manually.
So if i understand this correctry when i do this:

Code: Select all

    for y = 1, 2048 do
        for x = 1, 8192 do
                blob:writeU8(1)
        end
    end
i write bunch of 1's in order like this 1 1 1 ...
and when i do this :

Code: Select all

    local map = {}
    for y=1,2048 do
        map[y] = {}
        for x=1,8192 do
            map[y][x] = blob:readU8()
        end
    end
i create a table with 1's in each map[y][x] in same order i wrote 1's in the first loop?

Re: Saving/Loading big map table

Posted: Tue May 22, 2018 2:37 pm
by grump
Yes. Although using a table of tables is not the most efficient way to do this, it will work fine.

For maximum effciency use a plain, one-dimensional array and index like this:

Code: Select all

local map = {}
for y = 0, 2047 do
    for x = 1, 8192 do
        map[y * 8192 + x] = blob:readU8()
    end
end
Or even simpler:

Code: Select all

local map = {}
for pos = 1, 2048 * 8192 do
	map[pos] = blob:readU8()
end

-- looking up a x, y coordinate 
local type = map[(y - 1) * 8192 + x]
That's not Blob related though, just general performance advice. You'll get better cache hit rates when you do it like this.