Thank you and I'm happy to hear that it was useful. We of course have to start with at least an intro to what kind of language Odin is and how to do basic procedural things. :)
I love this comment 😂, I always compare magic to computers when giving talks. Computers are very similar to magic, seems crazy but when you look into it you are often amazed by just how simple it is.
The main thing to understand is that it's an argument that gets passed automatically to all your functions and that this argument gets used in functions, very often via the `allocator := context.allocator` pattern. If there are any specific questions about it, feel free to post them here or jump into the Odin discord: discord.com/invite/sVBPHEv
`temp_allocator` is really the same thing as the `allocator` except it signals more clearly that the allocation is used in a temporary context. One use case for it would be to have frame-local allocations in a game, for example, that we are guaranteed to lose and not need past the frame boundary. At the end of a frame we would then free/deallocate the `temp_allocator`.
I'd be curious about conditional build logic. For example, a Debug build would have lots of logging while a Release build would not. What's the cleanest way to do that so that your not paying the performance penalty of logging in the Release build?
Make a debug_log function that just calls log.debugf and use the @(disabled=ODIN_DEBUG) attribute. Also, use #caller_location to overwrite the location of debugf.
Great question! Yes, when you set `context.allocator` (or anything in the current context) you only apply that change for the current scope and anything below in the call stack. This means we can basically say "Anything below this point will use this, but nothing 'above' us will be affected". Now, I do still think that it's generally better to pass allocators specifically as arguments to the functions we call in most cases, because it's really not that difficult or tiresome and it's much better for clarity, but `context.allocator` is still very good because it allows you to modify stuff below you in the call stack that doesn't itself allow for passing allocators.
While I haven't really looked into it very deeply I always just default to `mem/virtual` because it has way more options for your initialization needs, etc. All allocation will be virtual until you write on for example Linux because of copy-on-write, so I'm not sure it matters a whole lot.
Why would you explicitly pass an allocator when the allocator is passed through the context anyway? The allocator already defaults to context.allocator, why pass it at all? If you wanted to use a different allocator you could just change the one in the context for the duration of the procedure call, no?
Setting `context.allocator` is a very indirect way of doing it. Passing a parameter is clearer and doesn't require you to set your `context.allocator` value to something and then change it back if you want to use a specific allocator only for the call. In some cases you want to use a small byte buffer of a few megabytes as an arena for a call, for example, and it wouldn't make sense to ever set that as your `context.allocator` value, but it makes perfect sense to pass as a parameter. Having the parameter also helps document what allocates and what doesn't as well as make it trivial to test functions with a `Tracking_Allocator` to see that they don't leak memory.
Thank you. This is incredibly useful. I liked how you started with the basics.
Thank you and I'm happy to hear that it was useful. We of course have to start with at least an intro to what kind of language Odin is and how to do basic procedural things. :)
Even though I didn't completely understand, I am using context in my code. Currently it's a magic thing for me.
I love this comment 😂, I always compare magic to computers when giving talks. Computers are very similar to magic, seems crazy but when you look into it you are often amazed by just how simple it is.
The main thing to understand is that it's an argument that gets passed automatically to all your functions and that this argument gets used in functions, very often via the `allocator := context.allocator` pattern. If there are any specific questions about it, feel free to post them here or jump into the Odin discord: discord.com/invite/sVBPHEv
Would be nice to also talk about the temp_allocator as well
`temp_allocator` is really the same thing as the `allocator` except it signals more clearly that the allocation is used in a temporary context. One use case for it would be to have frame-local allocations in a game, for example, that we are guaranteed to lose and not need past the frame boundary. At the end of a frame we would then free/deallocate the `temp_allocator`.
I'd be curious about conditional build logic. For example, a Debug build would have lots of logging while a Release build would not. What's the cleanest way to do that so that your not paying the performance penalty of logging in the Release build?
I do have plans on making a video on how this works in the future, but I'll have to research it properly first.
Make a debug_log function that just calls log.debugf and use the @(disabled=ODIN_DEBUG) attribute. Also, use #caller_location to overwrite the location of debugf.
Does the context get reset when you leave the function that changed it?
Great question! Yes, when you set `context.allocator` (or anything in the current context) you only apply that change for the current scope and anything below in the call stack. This means we can basically say "Anything below this point will use this, but nothing 'above' us will be affected".
Now, I do still think that it's generally better to pass allocators specifically as arguments to the functions we call in most cases, because it's really not that difficult or tiresome and it's much better for clarity, but `context.allocator` is still very good because it allows you to modify stuff below you in the call stack that doesn't itself allow for passing allocators.
I've noticed that in Odin we have Arena in mem and in mem/virtual packages. Is there any difference in usage?
While I haven't really looked into it very deeply I always just default to `mem/virtual` because it has way more options for your initialization needs, etc.
All allocation will be virtual until you write on for example Linux because of copy-on-write, so I'm not sure it matters a whole lot.
Why would you explicitly pass an allocator when the allocator is passed through the context anyway? The allocator already defaults to context.allocator, why pass it at all? If you wanted to use a different allocator you could just change the one in the context for the duration of the procedure call, no?
Setting `context.allocator` is a very indirect way of doing it. Passing a parameter is clearer and doesn't require you to set your `context.allocator` value to something and then change it back if you want to use a specific allocator only for the call. In some cases you want to use a small byte buffer of a few megabytes as an arena for a call, for example, and it wouldn't make sense to ever set that as your `context.allocator` value, but it makes perfect sense to pass as a parameter.
Having the parameter also helps document what allocates and what doesn't as well as make it trivial to test functions with a `Tracking_Allocator` to see that they don't leak memory.