#[SOLVED] Help with stack overflow warning.

10 messages · Page 1 of 1 (latest)

obtuse plank
#

Hi!
I'm trying to setup a basic sparse-set based ECS for game development.

I tried the following approach:

GDOCTestWorld :: struct {
    position_components : ecs.ComponentContainer(PositionComponent, MAX_ENTITIES),
    velocity_components : ecs.ComponentContainer(VelocityComponent, MAX_ENTITIES),
    drawable_components : ecs.ComponentContainer(DrawableComponent, MAX_ENTITIES),
    collision_components : ecs.ComponentContainer(CollisionComponent, MAX_ENTITIES),
    using base : ecs.World
}
main :: proc() {
    world := GDOCTestWorld {}
    [...]
}

So, ComponentContainer is a sparse-set and I create and insert the containers manually in a sructure with subtype of ecs.World.
When I run the project, I'm given the following warning: Warning: Declaration of 'world' may cause a stack overflow due to its type 'GDOCTestWorld' having a size of 400096 bytes
Searching here, on discord, I learned that structs area allocated to the stack, so I can see a reason for the warning, so I tried to change my struct, now by using the following approach:

GDOCTestWorld :: struct {
    position_components : ^ecs.ComponentContainer(PositionComponent, MAX_ENTITIES),
  [...]
}


main :: proc() {
    world := GDOCTestWorld {
        position_components = new(ecs.ComponentContainer(PositionComponent, MAX_ENTITIES)),
        [...]
    }
    [...]
}

And now it still works, but without the warning.
But, shouldn't pointers be always pointing to something? I didn't create the "something" anywhere... Where are the concrete values the pointer are pointing to?
Am I missing something? Is there a better approach than mine?
I'm not familiar with manually memory management languages, I'm still figuring things out.

gentle quartz
#

new allocates space for the ComponentContainer on the heap and returns a pointer to it. It'll continue to exist until you call free on the pointer

#

Another alternative would be to make the whole GDOCTestWorld a pointer, rather than each individual ComponentContainer. Might be a little less new/free boilerplate in that case, and will still resolve the issue.
Basically, your original setup but instead of

world := GDOCTestWorld {}

you do

world := new(GDOCTestWorld)
defer free(world)
obtuse plank
#

Wow, that would be really nice to not have to manually free every component container on world.

#

Thanks for explaning the idea of creating a pointer to memory allocated on the heap. I think I can understand now

gentle quartz
#

Local variables are stored on the stack, so when you try to declare a local variable of type GDOCTestWorld, it needs to put that entire struct (~400 KB, based on the error message) on the stack. The stack is managed automatically, so when the function (actually, the scope) exits that space gets reclaimed, but that space is also rather limited (typically, a few MB total). When you create it through new, though, it allocates space for it somewhere on the heap and returns a pointer to it. Space on the heap is only limited by how much memory is available to your program, but it's not managed automatically. You need to tell it when you no longer need things.

#

new is a way to allocate space for a single value on the heap, and you use free to release it when you're done.

Some of Odin's built-in types (maps and dynamic arrays) also allocate space on the heap, but you use delete for those instead. make can be used to create those types, but you can also just declare them and start to use them, in which case they can allocate automatically (appending to a dynamic array or setting a key in a map will allocate space). You should still delete them when you're done with them, even if you didn't explicitly use make on them. You can also use make to manually allocate a slice on the heap, in which case it should also be deleted. Slices shouldn't always be deleted, though, only if you used make initially (or some other Odin function allocated it for you).

Basically, the general rule (with some exceptions):

  1. Use new -> always use free on the resulting pointer (when you're done with it)
  2. Use make -> always use delete on the resulting slice/map/dynamic array (when you're done with it)
  3. Use a map -> use delete on it (when you're done with it)*
  4. Use a dynamic array -> use delete on it (when you're done with it)*
  5. Use a slice -> only delete it if you originally got it from make

* It's technically fine not to if nothing is ever added, but it's perfectly acceptable and often simpler to just call it regardless.

#

Trying to use something after you've freed or deleted it will result in strange behavior and crashes, as will trying to free or delete it more than once, which is where care is required. Don't free something you're still using!

obtuse plank
#

Changed my code to your approach and works like a charm. Thanks for the explanations, I'll keep all of your tips in use!
Now I'm gonna try to implement quadtrees for collision detection.
I'm in love with Odin language and community haha.

#

[SOLVED] Help with stack overflow warning.