There also small_arrays from "core:container/small_array", a small array is struct with a fixed_array and a length, you set the max_size of the array and then you have acess to procedures that are associated with dynamic array: append, unordered_remove, ordered_remove... but still keeping the array on the stack.
As to dynamic arrays growing, pretty sure it doubles the capacity + a little bit extra. I think a good strategy is to grow dynamic arrays aggressively and only shrink them if manually requested. This is done because memory is borderline free, and you normally only need to release memory when the whole dynamic array is freed. From odin source: `cap := 2 * array.cap + max(8, item_count)`
Thank you! Errors in Odin work just like any other value. You return an extra bool, enum or union from the struct that can have some error happen and then you act on the error. There are some special syntax you can use to make handling errors easier, such as: if data, ok := os.read_entire_file("file.txt"); ok { // in here ok is true and data is possible to use } Above read_entire_file returns two values: the data and a bool that says if everything went OK. After the ; there is the actual condition of the if statement. It checks if `ok` is true. If it is then we can use data inside the braces.
@@karl_zylinski Ah wonderful. My only nitpicks thus far is that: 1. Exported functions, methods, etc, aren't Capitalized like in Go 2. Error handling is not as aggresive as in Go
I'm confused about the difference between a slice an and array and when you would want to use one over the other? They just seem very interchangeable to me. Like, `position[3] f32` makes this {0, 0, 0} But, what would `ints = make([]20, 128)` make 128 0s same as the normal fixed array? Is the notion to just use slices for function calls and things like that? Why would you ever want to clone a slice if so?
A slice can be seen as "a window into another array". A slice can look into parts of a fixed array or look into parts of a dynamic array. You can use them as a generic way to pass the data of an array or parts of it into a procedure (function). This creates a new fixed array: position: [3]f32 The above does not dynamically allocate any memory. If you write the above within a procedure then the memory of that fixed array lives within the running procedure and dies when it finishes running. When you copy the fixed array variable, all the data in the fixed array is copied also: position: [3]f32 position2 := position position2[0] = 5 The last line only affects position2. There's no shared data between position and position2. However if you do this: position: [3]f32 pos_slice := position[:] pos_slice[0] = 5 Then the first element of position has changed. As I explain in the video the slice has a pointer inside to the data it looks at. In this case that pointer points to the beginning of position. So the slice is "looking into" position and modifying elements of the slice modifies elements of position. It's a window into another array. However, there is an exception to this "looking into an array". Slices can have their own data that doesn't look into anything else. If you do this: ints := make([]int, 128) Then you've made a slice that doesn't look into any other array, but it actually has its own dynamically allocated data! When the procedure in which you run the line above finishes, the data still lives on, because it is dynamically allocated. You need to at some point deallocate that memory by typing delete(ints) The reason you want to make a slice that has its own memory or clone a slice so that it gets its own memory is because you need it to live on for as long as you desire. For example if you have ints: [128]int then ints will die the when the current procedure dies but if you do ints: [128]int my_slice := slice.clone(ints[:20]) Then ints will still die when the procedure finishes, but my_slice now contains a clone of the first 20 elements. And that clone is dynamically allocated, it will live on until you run delete() on it! Also, typing ints := make([]int, 128) ints2 := ints does not make a copy of the data. This is again, as I explained in the video, because the slice `ints` has data pointer inside it. So both `ints` and `ints2` have the same data pointer inside, so no data is copied. Changing one changes the other. Use slice.clone to copy it. So in a sense, you can see how fixed arrays are very simple and copy in a simple way. That's why they are often used for things like Vector2 and Vector3 types, i.e. coordinates in 2 or 3 dimensions. They copy easily and don't do dynamic memory allocation. Dynamic memory allocation is slow and should be avoided.
@@karl_zylinski Thank you! I guess it just seems odd to me that a slice in this language seems to both be some kind of array list from java but also a way to do pointer arithmetic from C. Like it almost seems like a slice on the stack is for something totally different than a slice on the heap.
@@bomber5671 You are correct that it can be used in two different ways that are unrelated. You can think of a slice that is allocated using make as a "dynamically allocated array of fixed size". If you do ints := make([]int, 128) then there's actually another way to get the same result: ints_arr := new([128]int) ints := ints_arr[:] ints_arr is a dynamically allocated fixed array of 128 ints. This second example may make more sense when you think about how to make a dynamically allocated fixed array and then turn it into a slice. But the end result is identical. And in most cases you'd want something with of type []int and not of [128]int because the later is too specific to use with generic procs etc. So it's more convinient to do ints := make([]int, 128) right away and think of it as a way to make "dynamically allocated arrays of fixed size". Now to free the memory of something allocated with new, you'd usually use free: ints_arr := new([128]int) ints := ints_arr[:] // later free(ints_arr) However, since delete(ints) also just frees the contained memory block, you can in this case also use that: ints_arr := new([128]int) ints := ints_arr[:] // later delete(ints) This shows that this style is truly identical to using make([]int, 128)
@@karl_zylinski Oh okay thanks that makes a little more sense to me now. I can see how a slice would take that functionality on now. Thank you! Appreciate you taking the time to write this up
arr: [dynamic]proc(int, f32) -> bool For an array of procedures that take an int, an f32 and then return bool Or replace dynamic with a number to get a fixed array
I am working on a music player in Odin with Raylib and I found that Raylib can't set the progress of the music. It only plays from start to end. Do you have other recommendations for music playing in Odin?
If you are making a program to play the music and have control over the position in the music via some slider or something, then I think you need to control the playback yourself. I don't have any specific ideas, but in `vendor:miniaudio` you find stuff to play audio (it's the stuff that raylib wraps). Perhaps you can get more control by going to miniaudio directly. Other ideas are to use standalone libs like `vendor:stb/vorbis` (for .ogg files) and then other libs for loading `.mp3` etc... And then you interface directly to the sound API of your operating system, writing things to a buffer than is played. That way you can get maximal control over what is sent to the sound API.
Great video as usual Karl. Thank you. Might I make a small critical observation though? You often use the term "pointer" when what you mean is "address". Technically a pointer is a variable that holds an address. As you are aiming to educate and are writing a book I personally think the detail matters. Am I being too pedantic? Keep up the good work.
Thank you. In the book more care is taken around this. I'll try to do better in the videos too. But it's almost impossible to say the correct thing at all times when speaking, unless you script the video completely, in which case the video will be terrible instead.
There also small_arrays from "core:container/small_array", a small array is struct with a fixed_array and a length, you set the max_size of the array and then you have acess to procedures that are associated with dynamic array: append, unordered_remove, ordered_remove... but still keeping the array on the stack.
As to dynamic arrays growing, pretty sure it doubles the capacity + a little bit extra. I think a good strategy is to grow dynamic arrays aggressively and only shrink them if manually requested. This is done because memory is borderline free, and you normally only need to release memory when the whole dynamic array is freed.
From odin source: `cap := 2 * array.cap + max(8, item_count)`
I started ODIN and asked myself one question: How to handle errors in Odin? BTW u are by far
the best resource learning ODIN!
Thank you!
Errors in Odin work just like any other value. You return an extra bool, enum or union from the struct that can have some error happen and then you act on the error.
There are some special syntax you can use to make handling errors easier, such as:
if data, ok := os.read_entire_file("file.txt"); ok {
// in here ok is true and data is possible to use
}
Above read_entire_file returns two values: the data and a bool that says if everything went OK. After the ; there is the actual condition of the if statement. It checks if `ok` is true. If it is then we can use data inside the braces.
@@karl_zylinski Ah wonderful. My only nitpicks thus far is that:
1. Exported functions, methods, etc, aren't Capitalized like in Go
2. Error handling is not as aggresive as in Go
thank you for the video, looking forward to the book :)
Clearly explained content, as usual. Thanks for sharing!
Thank you magistern!
Thank you so much! I find your videos like these so helpful. 😊
I'm confused about the difference between a slice an and array and when you would want to use one over the other? They just seem very interchangeable to me.
Like, `position[3] f32` makes this {0, 0, 0}
But, what would `ints = make([]20, 128)` make 128 0s same as the normal fixed array?
Is the notion to just use slices for function calls and things like that? Why would you ever want to clone a slice if so?
A slice can be seen as "a window into another array". A slice can look into parts of a fixed array or look into parts of a dynamic array. You can use them as a generic way to pass the data of an array or parts of it into a procedure (function).
This creates a new fixed array:
position: [3]f32
The above does not dynamically allocate any memory. If you write the above within a procedure then the memory of that fixed array lives within the running procedure and dies when it finishes running.
When you copy the fixed array variable, all the data in the fixed array is copied also:
position: [3]f32
position2 := position
position2[0] = 5
The last line only affects position2. There's no shared data between position and position2.
However if you do this:
position: [3]f32
pos_slice := position[:]
pos_slice[0] = 5
Then the first element of position has changed. As I explain in the video the slice has a pointer inside to the data it looks at. In this case that pointer points to the beginning of position. So the slice is "looking into" position and modifying elements of the slice modifies elements of position. It's a window into another array.
However, there is an exception to this "looking into an array". Slices can have their own data that doesn't look into anything else. If you do this:
ints := make([]int, 128)
Then you've made a slice that doesn't look into any other array, but it actually has its own dynamically allocated data! When the procedure in which you run the line above finishes, the data still lives on, because it is dynamically allocated. You need to at some point deallocate that memory by typing
delete(ints)
The reason you want to make a slice that has its own memory or clone a slice so that it gets its own memory is because you need it to live on for as long as you desire. For example if you have
ints: [128]int
then ints will die the when the current procedure dies
but if you do
ints: [128]int
my_slice := slice.clone(ints[:20])
Then ints will still die when the procedure finishes, but my_slice now contains a clone of the first 20 elements. And that clone is dynamically allocated, it will live on until you run delete() on it!
Also, typing
ints := make([]int, 128)
ints2 := ints
does not make a copy of the data. This is again, as I explained in the video, because the slice `ints` has data pointer inside it. So both `ints` and `ints2` have the same data pointer inside, so no data is copied. Changing one changes the other. Use slice.clone to copy it.
So in a sense, you can see how fixed arrays are very simple and copy in a simple way. That's why they are often used for things like Vector2 and Vector3 types, i.e. coordinates in 2 or 3 dimensions. They copy easily and don't do dynamic memory allocation. Dynamic memory allocation is slow and should be avoided.
@@karl_zylinski Thank you!
I guess it just seems odd to me that a slice in this language seems to both be some kind of array list from java but also a way to do pointer arithmetic from C. Like it almost seems like a slice on the stack is for something totally different than a slice on the heap.
@@bomber5671 You are correct that it can be used in two different ways that are unrelated.
You can think of a slice that is allocated using make as a "dynamically allocated array of fixed size".
If you do
ints := make([]int, 128)
then there's actually another way to get the same result:
ints_arr := new([128]int)
ints := ints_arr[:]
ints_arr is a dynamically allocated fixed array of 128 ints. This second example may make more sense when you think about how to make a dynamically allocated fixed array and then turn it into a slice.
But the end result is identical. And in most cases you'd want something with of type []int and not of [128]int because the later is too specific to use with generic procs etc. So it's more convinient to do
ints := make([]int, 128)
right away and think of it as a way to make "dynamically allocated arrays of fixed size".
Now to free the memory of something allocated with new, you'd usually use free:
ints_arr := new([128]int)
ints := ints_arr[:]
// later
free(ints_arr)
However, since delete(ints) also just frees the contained memory block, you can in this case also use that:
ints_arr := new([128]int)
ints := ints_arr[:]
// later
delete(ints)
This shows that this style is truly identical to using make([]int, 128)
@@karl_zylinski Oh okay thanks that makes a little more sense to me now. I can see how a slice would take that functionality on now. Thank you! Appreciate you taking the time to write this up
Hi Karl. What about 2d arrays in Odin?
You can do [20][20]int to make a fixed 2D array. But I usually recommend to make a 1D array and index it using `idx := y*width + x`
How do you declare an array of functions?
arr: [dynamic]proc(int, f32) -> bool
For an array of procedures that take an int, an f32 and then return bool
Or replace dynamic with a number to get a fixed array
I am working on a music player in Odin with Raylib and I found that Raylib can't set the progress of the music. It only plays from start to end. Do you have other recommendations for music playing in Odin?
If you are making a program to play the music and have control over the position in the music via some slider or something, then I think you need to control the playback yourself. I don't have any specific ideas, but in `vendor:miniaudio` you find stuff to play audio (it's the stuff that raylib wraps). Perhaps you can get more control by going to miniaudio directly.
Other ideas are to use standalone libs like `vendor:stb/vorbis` (for .ogg files) and then other libs for loading `.mp3` etc... And then you interface directly to the sound API of your operating system, writing things to a buffer than is played. That way you can get maximal control over what is sent to the sound API.
SeekMusicStream() ?
Great video as usual Karl. Thank you.
Might I make a small critical observation though? You often use the term "pointer" when what you mean is "address". Technically a pointer is a variable that holds an address. As you are aiming to educate and are writing a book I personally think the detail matters. Am I being too pedantic?
Keep up the good work.
Thank you.
In the book more care is taken around this. I'll try to do better in the videos too. But it's almost impossible to say the correct thing at all times when speaking, unless you script the video completely, in which case the video will be terrible instead.