digitalmars.D.learn - Example usage of the core.sync classes
- Matt (12/12) Jan 02 2015 I'm trying to write a small 3D engine, and wanted to place the
- Vlad Levenfeld (13/25) Jan 02 2015 My personal favorite method is to use the primitives in
- Benjamin Thaut (20/31) Jan 02 2015 I have to agree with Vlad here. The usual way is to do double buffering....
- Matt (4/48) Jan 03 2015 Right, I've been looking at core.atomic, but it has very little
- Vlad Levenfeld (22/25) Jan 03 2015 Could you be more specific about what you need help
- Matt (21/46) Jan 03 2015 What I mean is that I don't understand what atomicStore,
- Vlad Levenfeld (29/51) Jan 03 2015 Atomic operations are guaranteed to happen in "one step". For
I'm trying to write a small 3D engine, and wanted to place the physics in a separate thread to the graphics, using events, possibly std.concurrency, to communicate between them. How, then, do I pass large amounts of data between threads? I'm thinking the physics world state (matrix for each object, object heirarchies, etc) being passed to the graphics thread for rendering. I'd assumed that I would use Mutex, or ReadWriteMutex, but I have no idea how to build code using these classes, if this is even the right way to go about this. I would really appreciate any pointers you can give. Many thanks
Jan 02 2015
On Friday, 2 January 2015 at 17:39:19 UTC, Matt wrote:I'm trying to write a small 3D engine, and wanted to place the physics in a separate thread to the graphics, using events, possibly std.concurrency, to communicate between them. How, then, do I pass large amounts of data between threads? I'm thinking the physics world state (matrix for each object, object heirarchies, etc) being passed to the graphics thread for rendering. I'd assumed that I would use Mutex, or ReadWriteMutex, but I have no idea how to build code using these classes, if this is even the right way to go about this. I would really appreciate any pointers you can give. Many thanksMy personal favorite method is to use the primitives in core.atomic with a double or triple buffer. To double buffer, keep two buffers in an array (data[][] or something) and an integer index that points to the active buffer, then use atomicStore to update active buffer index when you want to swap. Triple buffering can improve runtime performance at the cost of more memory, and in this case you will need two indices and two swap methods, one for the producer and another for the consumer. I use cas with a timed sleep to update the indices in this case. Take it with a grain of salt, I'm far from a pro, but this has worked well for me in the past. I can post some example code if you need it.
Jan 02 2015
Am 02.01.2015 um 19:45 schrieb Vlad Levenfeld:My personal favorite method is to use the primitives in core.atomic with a double or triple buffer. To double buffer, keep two buffers in an array (data[][] or something) and an integer index that points to the active buffer, then use atomicStore to update active buffer index when you want to swap. Triple buffering can improve runtime performance at the cost of more memory, and in this case you will need two indices and two swap methods, one for the producer and another for the consumer. I use cas with a timed sleep to update the indices in this case. Take it with a grain of salt, I'm far from a pro, but this has worked well for me in the past. I can post some example code if you need it.I have to agree with Vlad here. The usual way is to do double buffering. So you have two buffers which hold all the data that the physics simulation passes to the game logic. While the physics simulation is computing the next frame, the game logic can use the results from the last frame. The only point where sinchronisation is neccessary is when swapping the buffers. You have to ensure that both the game logic and the physics thread are done with their current buffer and then sawp them. The only downside of this approach is, that you will get a small delay (usually the time taken for 1 frame) into the data you pass this way. Sometimes this approach is called the "exractor pattern". I use it to pass data from the game simulation to the renderer, so the renderer can render in paralell with the game simulation computing the next frame. You can find a example of my double buffered solution here: https://github.com/Ingrater/Spacecraft/blob/master/game/sources/renderer/extractor.d I had tripple buffering up and running at some point, but I went back to double buffering, because tripple buffering can cause micro lags and you don't want that. Kind Regards Benjamin Thaut
Jan 02 2015
On Friday, 2 January 2015 at 18:52:11 UTC, Benjamin Thaut wrote:Am 02.01.2015 um 19:45 schrieb Vlad Levenfeld:Right, I've been looking at core.atomic, but it has very little documentation, and it's territory I haven't explored, yet. Any chance of some pointers along the way?My personal favorite method is to use the primitives in core.atomic with a double or triple buffer. To double buffer, keep two buffers in an array (data[][] or something) and an integer index that points to the active buffer, then use atomicStore to update active buffer index when you want to swap. Triple buffering can improve runtime performance at the cost of more memory, and in this case you will need two indices and two swap methods, one for the producer and another for the consumer. I use cas with a timed sleep to update the indices in this case. Take it with a grain of salt, I'm far from a pro, but this has worked well for me in the past. I can post some example code if you need it.I have to agree with Vlad here. The usual way is to do double buffering. So you have two buffers which hold all the data that the physics simulation passes to the game logic. While the physics simulation is computing the next frame, the game logic can use the results from the last frame. The only point where sinchronisation is neccessary is when swapping the buffers. You have to ensure that both the game logic and the physics thread are done with their current buffer and then sawp them. The only downside of this approach is, that you will get a small delay (usually the time taken for 1 frame) into the data you pass this way. Sometimes this approach is called the "exractor pattern". I use it to pass data from the game simulation to the renderer, so the renderer can render in paralell with the game simulation computing the next frame. You can find a example of my double buffered solution here: https://github.com/Ingrater/Spacecraft/blob/master/game/sources/renderer/extractor.d I had tripple buffering up and running at some point, but I went back to double buffering, because tripple buffering can cause micro lags and you don't want that. Kind Regards Benjamin Thaut
Jan 03 2015
On Saturday, 3 January 2015 at 15:44:16 UTC, Matt wrote:Right, I've been looking at core.atomic, but it has very little documentation, and it's territory I haven't explored, yet. Any chance of some pointers along the way?Could you be more specific about what you need help understanding? You can implement a very simple double buffer like so: synchronized final class DoubleBuffer (T) { T[][2] data; uint write_index; shared void swap () { atomicStore (write_index, (write_index + 1) % 2); (cast()this).buffer[write_index].clear; } shared property write_buffer () { return (cast()this).buffer[write_index][]; } shared property read_buffer () { return (cast()this).buffer[(write_index + 1) % 2][]; } } Just append to write_buffer, call swap, then read from read_buffer. Make sure you declare the instance shared. Benjamin's post links to a more robust and realistic implementation.
Jan 03 2015
On Saturday, 3 January 2015 at 22:10:49 UTC, Vlad Levenfeld wrote:On Saturday, 3 January 2015 at 15:44:16 UTC, Matt wrote:What I mean is that I don't understand what atomicStore, atomicLoad, etc. actually DO, although in the case of the two mentioned, I can hazard a pretty good guess. The documentation doesn't exist to tell me how to use the functions found in the module. I've never used any atomic functions in any language before. However, thank you for the simple double buffered example, I do appreciate it. How would another thread gain access to the instance in use, though? Am I able to pass references to instances of this class via events? 'synchronized' is also something I'm clueless about, and again the D documentation doesn't help, as the section in the class documentation basically says "synchronized classes are made of synchronized functions", without explaining what a synchronized function IS. The function section makes no mention of the synchronized keyword, either. Sorry, it feels like there's a load of stuff in the library and language to make multithreading easier, but with little to no explanation, so it feels like you're expected to already know it, unless I'm looking in the wrong places for my explanations. If I am, please do give me a kick, and point me in the right direction.Right, I've been looking at core.atomic, but it has very little documentation, and it's territory I haven't explored, yet. Any chance of some pointers along the way?Could you be more specific about what you need help understanding? You can implement a very simple double buffer like so: synchronized final class DoubleBuffer (T) { T[][2] data; uint write_index; shared void swap () { atomicStore (write_index, (write_index + 1) % 2); (cast()this).buffer[write_index].clear; } shared property write_buffer () { return (cast()this).buffer[write_index][]; } shared property read_buffer () { return (cast()this).buffer[(write_index + 1) % 2][]; } } Just append to write_buffer, call swap, then read from read_buffer. Make sure you declare the instance shared. Benjamin's post links to a more robust and realistic implementation.
Jan 03 2015
On Sunday, 4 January 2015 at 01:02:07 UTC, Matt wrote:What I mean is that I don't understand what atomicStore, atomicLoad, etc. actually DO, although in the case of the two mentioned, I can hazard a pretty good guess. The documentation doesn't exist to tell me how to use the functions found in the module. I've never used any atomic functions in any language before.Atomic operations are guaranteed to happen in "one step". For example, x += 1 will load the value of x into a register, increment it by 1, then write the value back to memory, while atomicStore (x, x+1) will cause all three steps to be executed as if they were instantaneous. This prevents a thread writing to an address that another thread just read from, causing that thread's view of the data to be silently outdated (aka a race condition, because the final value of x depends on which thread writes first).However, thank you for the simple double buffered example, I do appreciate it. How would another thread gain access to the instance in use, though? Am I able to pass references to instances of this class via events?You can if they're shared. You can pass the reference via message, or have the reference exist at a location both threads have access to. The two property functions in the example are meant to be used by the writing and reading thread, respectively. I go about the swap by letting the reader thread message the writer to inform it that it is ready for more data, at which point the writer thread is free to call .swap () once it is done writing (though you can do it the opposite way if you want).'synchronized' is also something I'm clueless about, and again the D documentation doesn't help, as the section in the class documentation basically says "synchronized classes are made of synchronized functions", without explaining what a synchronized function IS. The function section makes no mention of the synchronized keyword, either.A synchronized class has a hidden mutex, and any thread that calls a member method acquires the lock. The synchronized keyword isn't limited to classes, though - you can find a detailed description of synchronized's various uses here: http://ddili.org/ders/d.en/concurrency_shared.htmlSorry, it feels like there's a load of stuff in the library and language to make multithreading easier, but with little to no explanation, so it feels like you're expected to already know it, unless I'm looking in the wrong places for my explanations. If I am, please do give me a kick, and point me in the right direction.Yeah, I think the docs are meant to be a reference if you already have in mind what you're looking for, and just need to know how to wire it up. I recommend Ali's book (linked in the previous paragraph) in general as its the most comprehensive and up-to-date explanation of the language as a whole that I've yet seen (and its free).
Jan 03 2015