Log In  

I'm not surprised that I'm hitting the "out of memory" error when I put a big table of coordinates into the global scope...I'm figuring out how to deal with that right now. My question is why does it take up so much memory in the first place?

I created a table with indexes for x/y coordinates, which in turn correspond to objects in the game.

My map is made up of 16x16 tiles, so 8 per screen. When I have a 7x7 map, that's 3136 slots in the table containing tile information. That's a lot, I know. Each table element has attributes for the tile. When there are 8 attributes in the object, Pico8 shows memory at 70% - adding just one more key-value pair takes me over the limit.

How does adding a single pair take up 30% more memory?

for x=1,map*8 do

    for y=1,map*8 do
            o=0, -- This pair puts me out of memory. Remove it and we're under.

I know that memory is used up by everything so the 70% I'm seeing is not just the array, but it's certainly a big chunk of it. Even after stripping out everything except the array, it's still at 59%.

I'm not good with memory sizes or the math that goes into it or what uses up memory and how. I'm pretty sure I'm just going to have to accept that my game will have to have smaller maps, which is fine, but more so just for my own education, I'm wondering why this happens.

P#41937 2017-06-25 21:11 ( Edited 2017-07-14 14:13)

I'm not super knowledgeable about Lua memory allocation but I'm pretty sure you will at least get better CPU performance—and you might possibly get lower memory usage—if you use a 1-dimensional array instead of a 2D array, and just use math to translate the x/y coordinate into an index into that one big array. So like the same way the sprite sheet indicies work. I've written the math for that several times so I know it's not tricky to figure out but I don't remember it off the top of my head. Just multiply by the y and add the x modulus the column width or something :)

P#41939 2017-06-26 00:45 ( Edited 2017-06-26 04:46)

I thought that too so I did a test to just create a 1D array that was just as big, but that didn't save any memory at all. I did the 2D x/y to make lookups a little quicker (so I thought) only to hit the limit instead.

P#41945 2017-06-26 09:35 ( Edited 2017-06-26 13:35)

That's a lot of data for each array. What's it all being used for?

Usually what I would do is store only the tile id in the format of:

map[y][x] = tile_id

Will every tile need all 9 of those pieces of information? Only adding them as needed and then checking if that index is null during your lookup would save memory.

If your tx and ty are just the tile x and tile y, then you already have those without saving them inside the grid -- they're literally the address of the data. So if you're iterating over your tiles during your update and currently something like..

for x=0,#map do
    for y=0,#map[x] do
        if map[x][y][hp] <= 0 then
            set_state(map[x][y][tx], [x][y][ty], some_state)

you could instead do something like:

for x=0,#map do
    for y=0,#map[x] do
         if map[x][y][hp] != nil then
             if map[x][y][hp] <= 0 then
                 set_state(x, y, some_state)

In practice, you could save symbols by making an isnull function. (And a 'lookup property' function would help with that too):

function isnull(val)
  if val ==nil then
    return true
    return false
P#41947 2017-06-26 10:00 ( Edited 2017-06-26 14:26)

I'm doing some refactoring now to figure out which sets of data I really need versus offsetting to later checks. I'm stripping out what I can. Having the tile x/y is definitely one of those duped things I think I can get rid of...just need to find out where/why I used it and fix it.

Reading over your suggestions, even if I do something like map[y][x] = tile_id - I'd still need a table with all the tile data..? The tile_id is an index to another table with all that data, so won't that land me in the same problem?

Thanks for your code insights, they're great to think about.

P#41949 2017-06-26 11:15 ( Edited 2017-06-26 15:15)

What I was thinking is that your 'map' table is just a 2d list of tiles. But you can use it for other things too. Here's how you would do that..

Instead of just a 2d list of ints, you'll make a 2d list of tables (like you have now). If that tile needs extra data (HP, Unlocked, color, etc) then you will add it to a table at that location. If something doesn't need to be there, you won't add it, so a lookup will result in a nil value. All of your lookups will check to see if the value is nil or not before doing further checks.

Hopefully that helps. Let me know if you would want a code example; I might be able to explain it better using a cart.

P#41950 2017-06-26 11:25 ( Edited 2017-06-26 15:25)

Oh I gotcha...that makes sense. Basically as-needed rather than pre-filling with everything.

I tend to just keep all my objects with same attributes for sake of ease (and laziness) without having to worry about nils and such. And that approach has never bit me in the ass until now...so your approach is worth a shot for sure.

P#41952 2017-06-26 11:36 ( Edited 2017-06-26 15:36)

Good luck!

P#41953 2017-06-26 12:45 ( Edited 2017-06-26 16:45)

Some notes, probably directed less towards you two than other readers:

You generally don't need to compare to NIL when testing for it. You can treat NIL like a boolean expression. No need for a helper function when you can just say "IF VAR" or "IF NOT VAR".

The only time you need to be specific about VAR==NIL is when VAR is a boolean and you could mistake a valid FALSE for NIL.

It's also handy to use the "X = X_IN OR X_DEFAULT" boolean expression trick for initializing from possibly-nil value, where X will be set to the input if valid, otherwise the default.

Here's an example that would pull from a 1D-encoded grid that doesn't have anything stored in it except non-default values.

function getcell(x,y)
    local c=level_grid[x+y*level_width] or {}
    return {
        n=c.n or 0,
        f=c.f or 0,
        g=c.g or 0,
        h=c.h or 0,
        p=c.p or 0,
        o=c.o or 0,
        status=c.status or 0

So this way you can have a nil cell entry if all values are default, or a valid cell entry but only the non-default values inside.

You could even have a dynamic setter that would keep the grid minimal, if you need to change it on the fly:

--handy trick uses pairs() iterator to test for empty table
function is_empty(t) return not pairs(t)(t) end

function setcell(x,y,c_in)
    --copy non-default values into a new cell
    local c={}
    if(c_in.n != 0) c.n=c_in.n
    if(c_in.f != 0) c.f=c_in.f
    if(c_in.g != 0) c.g=c_in.g
    if(c_in.h != 0) c.h=c_in.h
    if(c_in.p != 0) c.p=c_in.p
    if(c_in.o != 0) c.o=c_in.o
    if(c_in.status != 0) c.n=c_in.status

    -- if we didn't set any non-default values above, then
    -- we should clear the cell entirely.
    if(is_empty(c)) c=nil

    level_grid[x+y*level_width] = c
P#42468 2017-07-14 10:13 ( Edited 2017-07-14 14:13)

[Please log in to post a comment]

Follow Lexaloffle:          
Generated 2023-03-22 20:12:28 | 0.008s | Q:22