❔ Unsafe Reading different types of structs from a byte array
I'm currently using the
Unsafe
class to help me read (and also write) different types of structs into a byte array. This is my read function so far:
It works for me, even if the byte array contains something like 3 ints, a byte, and a long after than (21 byte elements). But I've also read about possible alignment issues which I'm not very familiar with... will my code run into problems at some point?56 Replies
What problem does this solve? Where does the array come from? Are offset and size truly necessary?
To me this kinda just feels like
Unsafe.ReadUnaligned<T>(ref array[offset])
Or ReadAligned, whichever fits betterI'm trying to pack structs into a single byte array but it kinda needs to be high performance
I was just worried that at some point those Unsafe functions weren't going to function correctly though
I don't know much about how the
cpblk
instruction works apart from what it's supposed to doI would really recommend just using the MemoryMarshal functions for this, if you can
❯ Method: System.Runtime.InteropServices.MemoryMarshal.Read<T>(ReadOnlySpan<Byte>)
Reads a structure of type T out of a read-only span of bytes.
React with ❌ to remove this embed.
But why are you packing them into a byte array
What are you doing with it
So that I can employ a kind of double-buffering thing
2 threads requiring access to the same data, so I wanna use double buffering instead of accessing a single field
Then at some point, sync both threads and write the data from the primary thread into the secondary thread's array
Saves having to create proxy classes containing the exact same data
Like this?
that works, though you don't really need the size
oh true
you can just slice the span [offset..]
Why does this require use of byte[] instead of T[]?
Because the array can contain different types of structs
I was thinking of just using entirely pointers and using the HAlloc functions but that seems like a cardinal sin...
(this is also not great, to be clear, I just wanted to make sure that if you do use this, you use MemoryMarshal to do it, since it does the correct thing)
I mean, how many different struct types are we talking about here? You could always have a T[] and a U[] and a V[], for instance.
It would be safer and almost certainly faster.
int, float, double, long, Vector2, Vector3, Matrix4x4, enums, and probably more
how exactly do you know the layout of this array at any given time
It gets calculated in the static constructor
I kinda replicated WPF's property system idea with it
GetValue, SetValue, where the generic type is unmanaged
The MemoryMarshal class seems to work nicely, i'll just stick with that
You should really strive to do this without MemoryMarshal or Unsafe if possible. These are advanced scenarios that could bite you if you're not careful.
If your static constructor knows the layout for any given type, wouldn't that be the correct place to set up a CustomStruct[]?
What kind of custom struct?
Is this just for cross-thread communication? Or did I misunderstand
Pretty much yeah
MemoryMarshal for cross thread comms is fine. That's one of the reasons it exists. But you're still in charge of offset bookkeeping, etc., which introduced potential for bugs.
Hence why I am asking if there is any other way (with less potential for introducing bugs) that you can do this.
Maybe the answer is no, but it's worth investigating.
Do you have a usage example?
I know that unreal engine does what i'm trying to do but their code base is so massive that I can't really figure out exactly how they're doing it
Yeah sure, this is how I get and set values
Then this is how I could define the stuff
ObjectC and ObjectB's byte array would be 17 bytes, whereas ObjectAs would be 9
I'm so confused
Why not just use fields?
And have R3Property or whatever get / set the fields directly.
Idek
I coulda probably just used a virtual method called "WriteLiveData" or something like that
But that also has a performance hit for hierarchial objects like a scene graph
I thought it would be more performant to store a global list of updates that can be published that would automatically write the data from one byte array to another, and that list gets filled when you actually call the setter methods
Haven't implemented that part yet but that's my idea
That just seems like a really complicated way to say "I don't have an object with fields, I have a byte array."
If i did it with just fields I would have to do so much manual work to implement double buffering
What kind of work do you expect to have to do?
If i had a player object and I wanted to set their position, i'd have to update the 'live' data, and then enqueue some sort of command (when the 2 threads are synced) that copies the 'live' data to the cached data
And then the 2 threads can continue executing normally in parallel
And that command I'm pretty sure would have to be an object class meaning there would be a ton of GC pressure if there's 10000s of updates per second
Whereas with the system I made, I can just use a list of structs containing the object reference and property
So copy the fields instead of copying the byte array contents. This can be done as a single line of code.
Yeah but like...
Where do you think I should do that if I did go the fields way?
Wherever you're currently calling Array.Copy, I guess ?
I'd set the app into a 'synchronize' state where main thread is ready to transfer data to the other thread (render thread in my case), and that thread is just sitting idle
And now that's the perfect time to transfer the 'live' data to the 'cached' data
Or interlocked exchange the live & cached objects.
If I went the fields way, It would have to be a recursive function for something like a scene object
I can't modify any of the data that the render thread may use while it's currently doing things
Otherwise I would just wrap every property getter and setter in a lock block
My way just seems like the most convenient... unless your way is much easier but I just don't understand what you're trying to say lol
It kinda sounds like you already have to write manual recursive logic to deal with offsets being modified when you have scene objects. Otherwise multiple nested complex objects will all try to write to the same index in the array, since the index is stored in the R3Property itself.
All of the byte offsets are calculated automatically, so I never have to deal with that
I can just register properties and use them immediately
And when I implement the global list of update tasks, I can make the set functions add a command to that list that would automatically move the data (that I just set) to the cached data, at the appropriate time
All i'd need to do at that point is implement that thread synchronization and then iterate that global list
I'm so confused can you not just send an actual struct over? Or have a reference to a class or struct in the threads and write there? Or use Interlocked.Exchange as mentioned?
Where would I do that though?
Like why can you not just send a
?
Thats what I have but it would be a class not struct, then I would define the Vector3 Position property in there
So it isn't what you have
I mean it's the same kind of layout
Sort of...
ero
REPL Result: Failure
Exception: CompilationErrorException
Compile: 823.393ms | Execution: 0.000ms | React with ❌ to remove this embed.
I mean whatever you get the idea
Why would this not be valid
It would be valid, but I'd have to copy
foo
for every single object in the application that requires double buffered dataBut if I use my code, then I have a function like this
Which is called by this code
And that ProcessUpdates function would get called when the 2 threads are in sync
PushUpdate can get called at any time
I just feel like the likely overhead with properties is so much less than having a sort of recursive 'TransferValue' method, that would transfer all of the data for every object in the app that requires the double buffering
Example usage
SetValue accesses the live data, same with GetValue, but ReadValue accesses the cached data
Was this issue resolved? If so, run
/close
- otherwise I will mark this as stale and this post will be archived until there is new activity.