#archived-networking
1 messages ยท Page 51 of 1
It doesn't do bitpacking mind you
Which is the right approach, BTW
It's just called bytestream
I was reading and searching... I found out a guy using this but it seems to be crap to me... not sure:
And on top of a byte stream (like this one from Complex), you'll need to handle the higher level things yourself...
Yes
The thing I sent is literally only for converting primitives (and strings) to memory
Formatting the data to and from is for your own implementation
We use both byte and bit streams, with and without packing, depending on the case.
I understood there are 3 layers.... the stream... the packing and the handlers for the CMD right? I'm trying to solve the third because I'm planning to use libraries dealing with the streaming and packing
I'm currently writing some IL weaver that automatically adds read and write functions to whatever objects you want to serialize
But I'm still working on that
Uses generics/interfaces without virt function calls โค
The stream of data comes from your transport layer
could you suggest a good library for packing and unpacking packets?
yeah.... let's call them packets then
IE, segments of memory
You convert those to your actual structured objects (preferably structs)
And pass those down to your entities that need their data
preferably structs
Memory aligned ones, and stored in sequential buffers preferably (write your own heap allocator)...
stilll it is needed an unpacking library that does the parsing to my struct/interface type
Or use code gen
Or, if you are exclusively using blittable structs, use marshal.offset
Storethe required data at startup using reflection
And you can easily restructure the data
I think that was a very good question TBH. Funny there isn't a good library that does all that...
Soon TM
Properly fast.
thank you ... any suggestiong for the unpacked packer library (parsing bytes to types?)
Will you you adhere to DOTS standards or will it be a plain C# thing @weak plinth ?
If you don't need bitpacking you can use mine. Probably as fast as it gets in C#
@gleaming prawn I haven't worked with DOTS, so you'll have to explain that one
I'm not sure DOTS would make things faster here
I know.
The idea as far as it does is that you get a delegate that you hand your object and writer off to
I assume then it will just work over any set of struct types just fine.
And it writes the entire object to the buffer you supplied
Not requiring the C# subset that burst compiler uses on Unity, etc
Delegate is a dynamic method
ok
Probably faster using memory offsets
But mine works with classes and non-blittble structs
ok... we work with blittable, memory aligned, and custom allocator even, so we can do as fast as it can possible be I guess.
If you only use blittable, just use memory offsets
memory alignment doesn't matter for serialization
Oh is he a colleague of yours?
For blittable types, these unsafe collections are faster than the original .net counterparts.
Yes
Small world I guess ๐
Of course
mmmm thank you but I'm not sure I want to push it so much! ๐
It's best to use only blittable structs, unmanaged
I want to write clean and optimize but I don't need this criticality level
That's going to be the fastest for sure, and saves you that GC alloc
There are these two very very important reasons:
- absolutely blazing fast performance (for several reasons, including cache hits, less indirections/lookups);
- No GC allocs (and infamous GC spikes).
That's why in our case blittable/memory aligned/custom allocated is mandatory...
I still need to grab some custom allocator
Notice again wbonx, GC spikes would hurt you much more than the array vs switch difference
Luckily I've abstracted allocation away everywhere
Having 0 runtime allocations from gameplay loops is our #1 mandatory rule (not an optimization later, design rule).
I'm gettting convinced about using a switch
Careful with the network transport layer in that case... Example...
I agree with that rule
Say you use Complex's bytestream, all good, right?
Then if the transport layer copies the array somewhere to batch the network data safely, that's probably memory alloc.
That will be left to the GC (depending on the transport layer lib you use - I don't know how the new one behaves in this case).
But that's also very important.
that depends from the net libray... I'm using the unity new one
Yeah, I haven't worked with it yet either.
they are all quite similar at the end... I'm ok so far with it
Just check the allocs... Contrary to what some people said, I haven't heard bad things about it actually (the new transport thing).
I wasa cheking the byte stream lib... interesting.,.. but I need something to unpack on the other end now
unpack on the other end?
deserialize
There's a reader and a writer
this how our Serializers look like (an example:)
Aren't you using the memory offset as well then Erick?
Since I'm sure Freddie uses that
there will always be a batch of the buffer copied somewhere else... that's by definition in almost all the network libraies
Depends if it's mamanged or unmanaged
partial class RuntimePlayer
{
public Int32 Data;
partial void SerializeUserData(BitStream stream)
{
stream.Serialize(ref Data);
}
}
Bitstream has writing/reading modes.
nice
I guess Complex's has something similar
We actually use two instances, yes
I like it... had already seen your lib and it was on the top of my choice
But this functions is called passing either one depending if this is send/recv
Of course you can use two functions (pack/unpack), or check stream.IsWriting?, etc
Maybe a silly question, but isn't the ref in this case adding overhead?
could you help me out with the usage of it on the reading side?
You need to copy 8 bytes instead of 4 no?
Passing as ref is one of the convenient syntax sugars ours has.
could you help me out with the usage of it on the reading side?
This function handles both.
If stream is in writing mode, if uses the REF to set the data from the stream. Otherwise, it reads from data and sets on stream buffer.
You need to copy 8 bytes instead of 4 no?
Yes... But in our experience this difference is minimal.
We do have the other syntax sugar, let me write here.
should bea actually faster with the ref
Yeah I understand that. Just making sure
I decided not to add ref, since the largest value is 8 bytes anyway
In my case
This also works:
partial class RuntimePlayer
{
public Int32 Data;
partial void SerializeUserData(BitStream stream)
{
if (stream.Writing)
{
stream.WriteInt(Data);
} else
{
Data = stream.ReadInt();
}
}
}
But I am using ref (optionally) with the new serializer I'm writing
Which is probably similar to what yours do, correct Complex?
Same if you split (de)serialization over two functions.
For the new lib, because you'll add the extentions, correct?
are you sure u need that if statement ?
I'm probably leaving the new lib pretty damn open
wouldn't be faster making the If implicit by passing two different ref type?
I was thinking about allowing users to add their own serialization functions based on type
The entire IL is added on first run anyway
IE you'd have something like
AddFunction<T>(Methodinfi func)
wouldn't be faster making the If implicit by passing two different ref type?
It's syntax sugar
Some people prefer one over the other.
can be sugary anyway without the IF ๐
And I was thinking about adding attributes as well, for bitpacking or something. Not sure yet
First experimenting on how to get reader and writer functions with IL
@weak plinth for a serialization lib... Hmmm... Attributes
Make sure you don't need anything that would lead to runtime checks...
I'd rather go for code generation.
A generator that spills blazing fast code...
I guess your library can't automaticly parse all types to a buffer right? I.E. I need to call a single method to get a single read and assign it to my struct right?
And then, yes... Attributes are useful AF.
There's runtime checks anyway.
You can't just throw any type down the serializer
If it's not known, it can't be serialized
IE non blittable types
But this is all on first startup
I mean, but at SOME point there will be a compiler that will work over this.
If you embed the method to the type, you can as well code generate that method.
So in runtime, you only call it with the stream/buffer
Startup runs, generates all the IL and spits out delegates that you can use, very crude
Will probably abstract a lot of that
true... I mean it takes just longer to implement
oh. ok
I mean, there's codegen\
I was looking for something in line with the library I posted
you're generating IL on the fly?
Well, only once on the fly
How does that goes with IL2CPP?
I don't know the details, but I know more libraries use IL weaving
I did wbonx... It's minimal, right?
Unity uses it itself afaik
Only problem I might have. I'm using Reflection.Emit, which isn't technically supported for .net standard 2 (2.1+)
But you can ref it
I think is qute not ptimized
No idea yet what the comp of Unity is going to be
not sure I should go for it
If you codegen yourself, blittable structs, directly to writer/reader funcs
That's going to be faster than what I'm making
That packer is storing enums for the type before each data... Not really needed if you know what you'll throw into the stream.
Since delegates, again means virt calls
But the writer/reader calls are not virt
It's still in early stages, haven't had much time
@gleaming prawn that's why it is a generic lib... but the implementation is odd... using the array resize and array of objects
Yeah, stay away from that lmao
๐
Not to sell my own product, but if you don't care about bitpacking, my lib is probably one of the best options out there
If you do care about bitpacking, I bet Erick has some nice classes for you
Looks very very incomplete
bitpacking means a parser of a package ?
No, bitpacking means you fill your buffer with only the bits you need
So writing a bool takes 1 bit instead of 8
And if you have an int that uses only max 10k, you can use like 6 bits instead of 32
problem is that it was used by a guy writing very interesting code... that's why I got confused.. this is the linki:
But that requires bit shifting
And yes, that class you've send @foggy vale looks like crap
The code looks like an absolute mess to begin with
And he boxes all primitives
since you checked the first unpacker... could you tell me why he uses an Objects[] ??
Complex's has a lot MORe than that one...
@weak plinth you talking about the first lib right ?
You can serialize any primitive with mine, or write an extension method for anything else
BTW @weak plinth , this:
public static void Write<T>(byte[] dest, int offset, T value) where T : unmanaged
{
fixed (byte* ptr = &dest[offset])
{ *(T*)ptr = value; }
}
Do you know that "fixed" cast becomes a function stack on IL?
In quantum, we IL-patch to make that a straight cast, with NO stack.
in release mode...
guys since now we know each other since 1h... could you tell me what IL stands for ?
I recommend anyone that if they need perf, they use the unmanaged writer
Intermediate Language
The .net assembly intermediate language (that goes into .net compiled DLLs)
@gleaming prawn Nice to see some more Exit faces in this chan
Exit ?!
Oh hey ๐
@jade glacier is writing a serializer as well
He actually inspired me to start working on one
Yes, Exit Games (I'm one of the core developers of Photon Quantum)
Emotitron I'm testing serializers... give me yours! ๐
I am talking with Tobi in a PM at the moment, doing work with Exit on PUN stuff
Tobi, I can hear his voice over the next room now...
@gleaming prawn I guessed by your nick... saw a very bad use of your first implementations in some games
Which implementations?
There are a few quantum games out there now, they are pretty good TBH. But it's always a work in progress, we learn every day (and the games themselves are not built by us).
But our internal stuff evolves as we go.
@foggy vale
https://github.com/emotitron/BitpackingTools
https://github.com/emotitron/TransformCrusher
Though the bitpacking tools are not a wrapper like most serializers, so it loses a small amount of speed not maintaining its own position value.
@gleaming prawn For example in Distrust they used photon... an early implementation I guess
Distrust is not using Quantum AFAIK.
went veeeery wrong
Photon isn't quantum
What is the team behind it?
Exit owns all the Photon products... Quantum is one of them
Photon has three main Unity products (PUN, which is free, Bolt which is now free, and Quantum).
we are talking about 5 years ago... wasn't this way
PUN = mimics the original unity networking tool, it's quite popular, and there are hundreds of thousands of developers using it, very little quality control from us;
Bolt = complete state transfer (free for 20 ccu, so worth a try for FPS, survivals, etc);
Quantum = fast deterministic predict rollback
5 years ago
Probably either PUN or even the lower level transport layer directly (photon realtime)
Quantum is pretty immune to "bad implementations"
Quantum was released january last year.
they just messed up something... it gave photon very bad advertisement
you should avoid this in future
Not 100% immune @jade glacier but I'd say it helps you go in the right direction.
I wouldn't claim it is immune.
By that I mean either it fails completely or it syncs your inputs I would assume?
you should avoid this in future
Not really easy when the product is free for everyone...
yes I understand
Same as Unity... They can't avoid bad "publicity" if someone uses it the wrong way.
How well stuff extrapolates I suppose falls into the implementation category
By that I mean either it fails completely or it syncs your inputs I would assume?
There's more to it...
@jade glacier will read your code.. thanks for the link!! ๐
You can fail to write a very fast implementation, it may happen...
Interesting
BUt I'm quite happy with the overall quality of some implementations.
From what I gathered, the main issue would just be creating a simulation that introduces difficult to extrapolate entities - resulting in some REALLY ugly resims
Thank you guys for the help! ... leaving for a while
AND there are a few very interesting titles being done now, under NDA.
@gleaming prawn looking forward to see them!
The couple titles fholm pointed out looked very solid
It's a bit more nuanced @jade glacier . I'm a bit more humble in that. I'm very proud of what we are doing, yes... But I tend to be very down to earth when checking each case.
@gleaming prawn you can mitigate by having a guy checking the forums for people having connection problems.... at the time that's how I learned about photomo and distrust
We do that wbonx
*sorry for the typos I'm dislessic
We have even crawlers checking that, so we get alerts (in case we go for the rescue attempt)
Good job then! ๐
But not always possible to check everything, or sometimes it's just difficult to fix due to being a very inexperienced team, etc.
PUN has the same issue Unity has - anyone can use it - and as such anyone can release a hot pile of garbage and have it associated with you
true
We've had people just trying to send "RPCs" with 1GB of data... In realtime.
And expecting that to just transfer immediately with almost no lag and all...:)
Quantum you get their team, so the quality standard I assume is going to be typically much higher?
then just give a different name to the two products... that's pretty basic business... otherwise you affect the brand
I can't imagine any team is just using Quantum without making use of support?
We've been in this for 15 years... Not really my area. But I have to say we're doing pretty well actually.
ti takes a couple of rounds on Capitalism 2 plus to learn that ๐
We're getting a lot more experienced teams now (specially with Quantum)
You know... many people can write a good library... few people can write a library that users can't screw up ๐
Currently, it has very little effect random forum complaints, honestly (but we still care and support anyone, of course).
I'm looking forward to getting an NDA in place with Exit, just so I can hear what is going on with Q
I'd say it's very very difficult, if not impossible to write something impossible to screw up
I have no idea... I only remember years ago this distress thing and maybe a couple of more minor
There is no net lib in Unityland that doesn't get negative forum hate.
Networking is hard. Trolls are harder.
There's nothing on the internet that doesn't.
again... thanks a lot for the help!! will go back at wok now
yw
aight, back to making codey bits. lates.
Erick, you might know this. I'm working as a Java full stack dev atm. But it's pretty damn boring. Does a company like yours accept new people unfamiliar in game-dev, that have to work remotely 40hrs a week?
Oh @weak plinth My final solution was to do some CodeGen. I generate an extension class for any class with the IPackable attribute.... and that has all of the serialization stuff pregen'd. Marshal was fragile, and reflection was slow.
We're always looking for hardcore programmers, although it's a long process normally.
remote work is not a problem.
What would this long process be? Lots of interviews and such?
Where are you based? I assume somewhere in Europe due to timezone?
Getting paperwork from Chrstiof mainly ๐
Netherlands
Lots of interviews and such?
Nah, just getting to know people better, seeing cool stuff (in game networking, or in our specific case hardcore cross platform determinism)
I started back in 2015, when I wrote a very very simple tutorial for PUN.
So a portfolio?
Very very simple, but the way I handled documentation and support caught the attention of management.
So a portfolio?
Nah, not formal like that... lets just say we'd not hire in the black...
It's like a pipeline... We have people that used to be customer now in our team (after they left their original studios), for example.
We used to have an interesting entry point...
@jade glacier I'm using IL weaving. Basically means the serialize/deserialize functions of the objects are delegates
In the past 2 years, we hired several small teams/developers to write samples to quantum.
BTW, maybe this is not the appropriate channel to this dicussion @weak plinth .
You can PM me if you want.
Lets stick to netcode here.
I am just rather than modifying a file with weaver, I am generating an extension for the class @weak plinth
but it also is just mapping delegates @weak plinth
I'm not modifying a file. Reflection.Emit offers dynamic methods
ah, yeah sounds like different path to a similar outcome
You can create methods on runtime, throw IL in there, and reference them as a delegate
I didn't like the Mono.Cecil idea of loading assemblies, modifying them and saving them again
I looked at some examples (Like Mirror's weaver), but it's such a spaghetti code
Mono.Cecil probably
There's no point in Mono builds...
Performance is ABISMAL.
So IL modifications should be done before build.
Fair point
I am hoping that the codegen will spare me from having to chase that dragon
In that case no IL weaving is going to work
We don't even bother with Mono anymore now that IL2CPP is getting stable.
Codegen brings a whole bunch of other problems
I mean, mono builds.
I used codegen before for exactly this. But all the build exceptions drove me insane
Well, do it by hand
We've been using code gen since day 1.
Like probably everyone does lmao
We code generate A LOT in quantum... It's a first class feature.
Build time?
Yes
Pre-build steps
Parsing our own DSL files.
To avoid developers having to deal with memory alignment, etc
(although we still let you do if you want to)
So you can mix and match as much as you want.
But most devs just 100% define game state data (entites, components, types) with our DSL.
Yes
I tried to avoid codegen for the longest time, but Marshal + OffsetOf + classes = hellzone
IF you only use blittable structs
Blittable structs is the ONLY solution for what we do anyway...
We use classes in just a couple of APIs.
GCHandle and IL2CPP were not friends
No GCHandle for us..:)
yeah, for Quantum, the path is clear on this stuff
How the hell do you use strings though?
I live in the world of "Users want to stick vars in a monoB and have them magically do stuff"
We only use strings in a few places (what we call the Asset DB - your game balancing sheet of your own custom types - classes)
Using your player name as the seed for your shotgun pattern... duh. @gleaming prawn
and what we call events (to communicate simulation stuff to the view layer - unity)
Using your player name as the seed for your shotgun pattern
You can do that, lol...
How would you send your player name to someone else?
player name isn't sim really
I know
We have a few entry points for polymorphism in these asset classes which mean, the rollbackable game state is 100% comprised of blittable data.
How would you send your player name to someone else?
You can do that, but that's not SIM, as said.
AND, you can use your own collections, data and make it rollbackable by implementing our partial entry point for a frame "deep copy".
Our internal, code generated game state (from the DSL), is a memory aligned buffer from our own custom allocators, so we can essentially "deep copy" with a few native memcpy calls...
That's as fast as it can be...
So basically, sim should literally only be blittable, ever
Ideally yes...
And this is not quantum specific...
This is a GOOD design principle in general.
This is what DOTS is all about.. It's pretty much the lessons you get from "Game Engine Architecture" by Jason Greggory, or what experienced studios use internally (check videos by Overwatch team, etc)
We just use the same principles (of course the OO conveniences exist in a few APIs).
Sounds like I'm going to have to make a serializer for both mono and IL2CPP
But a lot of what we do internally looks a lot more like C than C#.
I'd say if you want this to be used by experienced devs on games from now on, yes @weak plinth .
There's no point in making something for Unity now without working on IL2CPP.
IMHO. I think Unity guys would same the same.
IL2CPP does quite a good job, TBH
Very likely. But there's a lot of mono people still around
And I'm one of those ๐
I haven''t worked with DOTS at all
We compared performance of some of our math stuff (manually inlined) with:
Mono
.Net Framework (we can run ouside Unity)
.Net Core
IL2CPP
Full Native C with all optimizations on.
If you don't need DOTS, then don't use it. Unless you want to learn this specifically.
Yes, IL2CPP is not specific to DOTS...
Good to know
No matter what you use in Unity, IL2CPP is the default runtime NOW.
In the stuff we tested it's between 4-10x faster than mono builds.
Yes
That's been the default for IOS and consoles since a while.
Now it's the recommended runtime for all platforms.
it breaks.
It will only break at runtime though? lmao
You should not use something like that at runtime.
I think it fails to build,
IL2CPP would see the non valid APIs being used.
There's no IL anymore after IL2CPP.
we don't use anything IL2CPP doesn't support of course, it would not make sense.
So I haven't tested much.
Maybe somebody else knows... @stiff ridge nice to see you around..::)
Biggest headache for me with codegen is trying to deal with timing it. Like if I codegen in an extension class for ClassA ... the the dev deletes ClassA - there is a split second the log shows errors while my post compile callback handling deletes my extensions.
Chasing Unity's compiler is a pain in the ass
We compile outside of Unity.
Yeah, you guys live in that lovely ivory tower ๐
IL weaving is a beautiful tool. Shame it doesn't work in Unity then though...
Had big hopes
I have to muck around with the common folk in MonoB components ๐
I wouldn't describe it like that.
It seems every path leads to some ugly choices
I really don't want to bother with codegen anymore. I've had my fill last time, and that's been more than enough
Summary: beautifully convenient, but you need something else down the line for more complex stuff.
For this stuff I just make a template and fill in the field/reflection mappings... and call it done
I did as well, T4's
Interesting Complex... We're actually quite happy we chose this path.
Made code gen for packet handling basically
I didn't T4, did something a little more personal
I just hated making it
And AFAIK our customers as well. It's a strength in our tools.
since I wanted to be able to create a working template
But we have our own (Fredrik's) AST parser and compiler, which gives us full control.
Maybe T4 was just a horrible tool for the job. Maye that's why I didn't like it ๐
Maybe...
We don't use it...
Although we've recommended it for people who wanted to go even further down that rabbit hole.
https://hatebin.com/kbpblwjqzh
This steaming pile is my template... all the ugly though just gets deleted. Its just there so I can write working code to use to make my codegen code from.
The main benefit just being I can literally code an example of resulting code to scratchpad what I am doing. Other than that - its a total hack horseshit move.
this is what a Quantum DSL file looks like (for the developer, sample "entity" type):
entity Character3D {
use Transform3D;
use DynamicBody3D;
use CharacterController3D;
use Prefab;
}
nice and tidy
And this...
[StructLayout(LayoutKind.Explicit)]
public unsafe partial struct Character3D {
public const Int32 SIZE = 344;
public const Int32 ALIGNMENT = 8;
[FieldOffset(32)]
public CharacterController3D CharacterController3D;
[FieldOffset(112)]
public DynamicBody3D DynamicBody3D;
[FieldOffset(24)]
public Prefab Prefab;
[FieldOffset(56)]
public Transform3D Transform3D;
[FieldOffset(0)]
internal Entity _entity;
public EntityRef EntityRef {
get {
return _entity._ref;
}
}
public Int32 EntityIndex {
get {
return _entity._ref._index;
}
}
public EntityFlags Flags {
get {
return _entity._flags;
}
}
public override Int32 GetHashCode() {
unchecked {
var hash = 271;
hash = hash * 31 + CharacterController3D.GetHashCode();
hash = hash * 31 + DynamicBody3D.GetHashCode();
hash = hash * 31 + Prefab.GetHashCode();
hash = hash * 31 + Transform3D.GetHashCode();
hash = hash * 31 + _entity.GetHashCode();
return hash;
}
}
public static void Serialize(void* ptr, FrameSerializer serializer) {
var p = (Character3D*)ptr;
CharacterController3D.Serialize(&p->CharacterController3D, serializer);
DynamicBody3D.Serialize(&p->DynamicBody3D, serializer);
Prefab.Serialize(&p->Prefab, serializer);
Transform3D.Serialize(&p->Transform3D, serializer);
Entity.Serialize(&p->_entity, serializer);
}
}
Is the code generated struct...
I omitted some stuff because it was bigger than allowed.
There's a lot more we generate. It's very very convenient for the game developer in the end... Save Games, Snapshot based reconnects, replays (including full match and killcam), all this comes from two basic design principles:
- deterministic predict rollback
- code generation (Imagine just having to write serializers/packers by hand for all your types, just that).
My existence is writing serializers/packers by hand ๐
So you're afraid of loosing your job? ๐
Kidding
Codegen for your own benefit... So you can be as efficient as 20 yous.
It more is for this kind of stuff, so its not REALLY hand writing, its writing the things that automate the packing, so somewhere in between.
But like the serialization code for packing down a Mechanim is all kinds of fun
O(my)
Lots if inline bits that indicate what follows that indicates the packing that will be used and such
But that still is just once, and its all just based on parameters. I am not hand coding that over and over or anything
Transform crusher, this looks a bit like Bolt's approach for compression of fields.
Its pretty common now yeah, range based floats
yep
half floats is on that dropdown too
That's the default, so it "just works", but range based is better
We don't use floats, so we deal only with other types compression.
In quantum, ofc
Bolt does
yeah, I assume you employ less inline packing and more entire byte[] (or ulong[]) packing?
Or do you have per field packing?
Knowing fholm... the answer is "both"
yes, lol
But we don't do this prematurely
Specially because bandwidth in quantum is rarely an issue.
You are already dealing with small syncs yeah
Unless you create mammoth input data structs or have unusually high player counts.
But still important to be smart with META data (more important than the input struct itself)
you are only going to get so much reduction, but I can see schemes for that, like putting all of the inputs that tend to stay at zero at the far left
and putting all the high velocity inputs on the right, etc
We spent a lot more time designing and optimizing that for the several use cases than focusing on compressing the input data itself.
And this is a good advice IMHO for anyone writing HL netcode solutions:
- take care of your meta as much as your data...:)
Good luck preaching that to most of these networking channels
nearly all convos are "which is best" and which transport benchmarks the best... very little discussion of how to reduce your game to a simulation that consumes inputs and produces states.
So there is no meta, other than "which of these libs can I have the most connections to and cram the most data through"
That message is for a specific target (people who write some of these libs, but probably I don't need to say any of that)
Does anyone know how to create a fortnite style lobby
Either load a new scene when the game starts
Or easier yet, make some secluded area away from the map and throw everyone in there
That's all it is
@jade glacier just curious, but do you know why serialization seems to be so tedious to deal with for IL2CPP?
You mean related to what we were talking about earlier?
I think that was in reference to IL not being usable, so weaving and reflection.emit and such were out
For mean its tedious because I have opted for codegen, but that creates a whole bunch of icky race conditions with the Unity compiler, play and build timings.
@jade wharf
Huh, strange
I wonder what kind of IL couldnt be translated by a computer if a human could understand it
Not sure what is up with that, totally out of my wheelhouse
Hi, anyone familiar with PlayFab here for a quick discussion ?
@jade wharf That's not the point
If you compile with IL2CPP, it's native
That's why it's called IL TO C++
You can't then, after you are running a C++ application, inject IL into itself
Since it's no longer running IL
As soon as you pass the step of C++ code
It's over and done with using IL on runtime
So, then there's codegen or reflection left for serialization. One is tedious to make and one is slow as hell to use
@gleaming prawn Do you use bool anywhere or exclusively a blittable version?
We use Boolean inside game state, yes
Have to check what is the alignment for that inside the custom allocator.
But for network sends we use a bitstream, so we actually pack as 1 bit.
I figured that last bit
So your structs contain bools at places?
I wonder if it can cause problems for me, but I don't think so
Should not... We use everywhere, of course.
@jade glacier How were you doing the serializing of unmanaged types?
I can't seem to find a way to "bind" methods to fieldinfo's
Not unless I box values or something
I think I'm completely going the wrong direction with this ๐ค
its a long annoying chain at the moment
Basically I am constantly checking for changes to which classes/structs have my packing attribute - and then I generate an extension class for that class, that has all of the fields that will be serialized wired up to a generated serialization method.
Then since I can't hardwire that, that generated code has a runtime RunOnAwake initialization, that adds itself to a dictionary
I can't seem to use generics.
I've wasted a few hours on this
I think I need WriteInt, WriteByte, etc
yeah, I gave up on generics as well after hours of trying to make them happen
I have interfaces that indicate supported primitives for each packing attribute
So any packing attribute I write, will have those non-generic handlers
I tried just using Write<T>
If course
But you can't use this, unless you use dynamicinvoke
And I don't know the performance implications of that
but its a long chain, and trying to get that all into a nice delegate faceplants somewhere
Not even mentioning it only takes objects
My last performance ding is that I have to cast from Object to my Class type
yeah, just its there and its annoying me that I can't find a way for it not to be ๐
But otherwise, once its all running its just a delegate for the pack/unpack on the entire class
If you are using codegen, anything should be possible
You should be able to just use generic delegates there
and everything in that is normal code
The issue is I am not touching the actual class
I know
and you cant make extensions into delegates
So right now I have to produce a
{
var packable = obj as GarbageStart;
myIntPacker(ref packable.myInt, buffer, ref bitposition);
mybytePacker(ref packable.mybyte, buffer, ref bitposition);
}```
Kind of thing for each class
I haven't sorted out the way to avoid making that an object, since I am putting all of these delegates into a dictionary
There may be a way, I am just a mediocre C# coder and my knowledge of what can be done with delegates, anonymous and all of that is pretty limited.
So right now that cast makes this step work, and lets me get on to the stuff I do know, which is making all of wackadoo bitpacking attributes
But you have to assume the type has public members?
That's harsh
Its that or modifying the class itself as far as I can tell
I know this might be a bit late at this point
But maybe there is a way to inject IL before compilation?
Maybe, all of this is getting a bit lower level than I like to spend my time in. I am a UX and design guy. I am only doing any of this coding just to get me to that. So most of this is pretty much at my knowledge limits.
Do mind that people will have to break encapsulation to make the serializer work
Sure, but not as much minding that I won't finish this otherwise
This is networking for the masses, not ivory tower coding
The only non-IL way around that is to either modify the actual class.cs as far as I can see, or to have to make use of GetValue and SetValue
All of those aren't acceptable to me, so right now forcing public for networked fields seems like the lesser evil
If I can be completely honest
Maybe it's a good idea to just have people "deal with it"
You werent going to be?
If someone wants to serialize classes per say for network usage, they are stuck with mono
And you can use IL
And if someone wants actual performance in IL2CPP, they can only use structs anyway
You just exchanged forcing people down a path from one thing to another
None of the paths are perfect, and I just don't care that much. I have one objective and that is getting properly networked components into users hands
There may be a really good IL solution in all of this, but I don't touch IL and I don't like chasing rabbits
There might be, if you can, ahread of compilation, modify the assembly
I can always revisit that layer later if I decide I want to clean it up and have the time
That's the only thing I can think of
If you come up with the final ideal answer to all of this, I will love to know about it
you are better equipped to find that answer
Anything listed as AoT can't use Reflection.Emit
Part of me would love to dive down that rabbit hole. The practical side of me knows there are a dozen guys like you working this problem right now - and my skills are suited to be focused on the UX layer and component layer.
Also good to know that you can't use generic virtual methods
Since I am using those currently
So unless someone comes up with the right answer and tells me, I have to move on
public class SampleClass
{
[PackInt(0, 100)]
public int intA1;
[Pack]
public byte byteA1;
}```
This is workable
Nvm it's a bit more complicated than that
If you do solve this riddle, be sure to let me know ๐ I may need to revisit my choices.
Alright
It should be possible, but with a lot of hassle
You can use mono cecil to edit and save (back to disk) a dll
You can then run this code that does this during building, by using the buildpipeline
Does it handle itself correctly when you hit play?
The one major drawback right now with what I have is that if a player deletes a file that I had extended, the log shows errors for a second while my post compile code deletes the now invalid files
and I can't find any way to detect, delete and recompile before that error hits the log
Takes about this long to clear
It should handle itself correctly if done right
Since you are essentially building the modified assembly with all of the delegates and such you created
This is with codgen on the fly, rather than build time. Build time I couldn't get it to see the files if I made them with codegen on PreBuild callbacks
hello guys, can I ask a very simple question in parallel? Don't want to interrupt
I might take a look at this
Mono Cecil is pretty horrible to use since there's pretty much 0 docs
I see why Unet chose it, the alternatives are a nut scrape
Well, there are not really any (good) alternatives ๐
I was wondering what is the most efficient way to define a univoque ID of an object shared between server and clients, so that I.E. when a transform.position change package is exchanged the new position can be assigned to the right object
I just need passable and doesn't utterly break, which is where I think I am right now, so moving on for now to making my attributes. @weak plinth
its an instance I assume? @foggy vale
I kindof want to make an IL Unity and non-Unity version
The IL should be exactly the same
I guess at the beginning I have on the server an instantiate on the server that generate instance and tell the client to do the same giving it an ID. this objects are then stored on a sorted list?
@jade glacier yes
Is this with a particular library?
using yours ๐
Because just spawning does that
Oooh, well for SNS you are going to need to spawn normally
my stuff rides on top of PhotonView, NetworkIdentiy and NetworkedIdentity
poit is shall I use the native ID? isn't it heavy? Shall I then just instantiate on the server and share the unique ID with the clients?
your packers run on any library I guess
I'm using the new Unity library
it is like llapi
and your libs just to serialize packing transforms
yeah, they don't try to fight the existing libraries - it makes use of the netobj of the HLAPI being used
My stuff won't "just work" for that, since there is nothing there to just work on @foggy vale
but you can call my OnSerialize methods
that's what I'm doing
Its not throwing a wall of errors not having a recognized net lib?
The animatorsync specifically is worth the work you are doing, stripping out all of the net lib specifics
and just making use of the serialize/deserialize
question is just about what kind of ID shall I use, how to share it (i guess in a serialized struct passed on the connection... and how to store the ID, how is it more efficient, if they should be a sorted dictionary with as a value the ref to the instance
Writing that animatorSync yourself... do not recommend that.
That is kind of a bigger question, and for the new MP I am not fully equipped to give you the right answer
because you are basically making your own MLAPI
I don't need to synch any anim
yes right
but I need only to synch maybe a transform.... rest I don't care
yeah, you aren't going to be able to even think abut that part until you have your methods in place for spawning numbered objects
and yeah, the server is responsible for that
exactly... could you suggest an implemettation for that?
It will pick an unused ID, and then tell everyone to spawn a prefab and give it that ID
you have to make your own version of NetworkIdentity basically
right
what type of Id shall I use?
how many objects do you expect to max out at?
you will just have a dictionary
1000
likeDict<ushort, MyNetID> spawnedObjects
shall the ID be an integer... to be large enough but small enough to be in each packet? shall the dictionary be slowing things too much because of the lookup?
You can internally just us an int for cleanliness
but for networking you can bitpack or cast that down if you know the upper possible value
like 0-1000 you can cast down to a ushort
send 16 bits rather than 32
or you can use PackedBytes and not worry about it
but that's not really scalable.... 1000 is now much if you delete and create new instance and so on
just keep your values on the low end when possible so it can pack out the unused bytes
That is up to you, if you want your server code to aggressively recycle ids
what kind of list would you use? a dictionary isnt too slow?
dicts are quick
the quickest is an array, but then you have to allocate a MyNetId[1239239139] up front... no good
Just use a Dict, that's what it is there for ๐
a dict would do a search as big asthe dictionary for each packet would be crazy
how many objects do you think you will have in each packet there?
because if you have enough objects in a packet to crush your cpu with dict lookups... you are going to bring down the internet
don't know yet.... was thinking to keep it open and scalablke
Ok... so I instantiate on the server... give an ID and an int, then tell the clients to instantiate and send the same ID with the call ?
it is not about killing the CPU or the bandwidth, I'm just trying to find the best solution/cheapest solution
yeah, pretty much
the server and all clients instantiate the indicated prefab and assign the same id to your NetId component
do you have experience with this? using your own MLIB to synch and assign custom ID?
Dicts exist as the cheapest for looking things up by an index
are ou talking about indexed dictionary?
I don't write them, but all of the HLAPIs do this
it is more complicate since when you kill an instance you have the indexed/sorted dictionary that has to deal with that
That is all just later detail. For starters you just pull the next number.. have the server keep a counter of last assigned netid value
and increment it by one when you spawn
later you will want to replace that with something that keeps track of released IDs, so you can reuse them
Basically keep a queue or list of relased ids, and pull from that first rather than incrementing
that keeps your netid.value low - which makes it more compresable
but again... for later
To start, just have the server increment the assigned ID by one each spawn... done
and the ID in your example would be like the key of my dictionary and the instance ref would be the value?
I guess you could use an array as well instead of a dictionary and use a ringbuffer (or queue) to buffer free id's
Uses more memory, but guarantees no resizing
array aren't static and expensive to resize?
Every collection uses arrays
If you are going to have, say max 1024 networked objects
That's 1024 x 64 bits
8kb of memory used yay
And 1024 networked objects is already quite a lot
you have lots of options
so let's say... I make my 10K array... instantiate on the server... assign an int ID, send an instantiate msg with the int ID on the client and make the client add instantiate and assign the ref in the array at the same position right?
Normally, they should be in sync
main thing is don't paralyze yourself debating which collection to use. Just use the one that is easy. Refactor later if you decide it was a bad choice.
its all going to be a very contained bit of code, easy to refactor
If you are using the same collection of objects on server and client (which I assume you will)
it is critical to decide certain things early.... like the size of the ID variable... can't refactor that lateer
Any add/remove commands will deal with exactly the same data and id's
@weak plinth yes
Some things yes, this isn't one of those critical things
All that matters at this point is that the server produce an unused ID
If you add on the server on an empty collection, it will get id 0
Later you can get fancy with your handling for recycling
And if you send the client add
It will also give you this 0 as an id
Same for remove, or any value
right
I can't stop you, but these channels are full of dudes over engineering stuff for years - never producing any actual game code. Go the path of least resistance and get to a working state.. then evaluate.
And you can use a fixed queue for recycling id's
but what when I.E. things are reverted.... an onject has a collision and I need to know it's index?
Its ID is its index in the array
with an array I have to cycle through all the index to find out the index of the ref collliding
I mean, this convo for example... nearing 20 minutes just trying to pick a container type... that is cripplingly slow. Just pick one and go.
@jade glacier you are right... but I could waste time later by not thinking now
You are not making something where dictionary vs array is going to matter
Trust me ๐
it is also about the ID type or how to handle it... not jsut about the the container
Its going to be a long road though if you jawbone everything this hard
If you want to go lazy mode 5 milion
Throw an int or even an long as a dictionary key
Just only increment
No need for recycling
You arent going to make 4.2 billion objects anyway
You can refactor that down once you have a better idea what you actually need
The only reason for recycling is to be kind to the BytePacker
Just keep an id counter next to the dictionary that's a static value
but that is a later problem
4 bytes vs, what, maybe 2?
on a network for each packet.... that's something ๐
Just using 4 bytes at all times to define an ID isn't going to be bad
Then revisit it later
And that allows you to run through 4.2b objects
Which I assume you aren't going to do
You want a clean working prototype to optimize
Not a 4 weeks in and still be debating how to best make your NetObj
It would take you 48.8 days of recycling 1.000 objects per second
To cap out your integer
I thought there were better implementations... like using thre unity ID generator and chopping that down
yup, get to a working state using the most generic methods possible
Probably not going to happen
As I said yesterday, serialization takes much more cpu than message handling for instance
Dictionary access is crazy fast
again each implementation has problems downstream... the difference is try to predict them ... IMMO
And then spending a month on 5% of your entire product?
but you are right, maybe this is not going to be a problem later
Not worth it
There's a reason almost every game botched up somewhere
People run out of money and time
๐ฆ
And good enough is an actual thing
public static Func<DataStreamReader.Context, DataStreamReader, DataStreamReader.Context>[] _handlers = { MSG_Transform };
It is THE thing in networking
It's in every case of software, and probably engineering
If it meets the requirements ๐
Time to ship it
You are right maybe it is not a good quality to try to make things perfect and good looking... not sure
I would back up and start by creating your NetIdentity class that is going to be the heart of all of this
There's also a difference between end-user products and frameworks or libs
In the latter, I'd argue that performance is much more important
being an asset maker, I can attest to the pain in the assness of not knowing all of the use cases, and having to account for them
netidentity class is going to be ID and owner as a component on the instance, not much more I guess
It will start as that
and grow
it is your primary conduit for all serialization, ownership, authority etc
@weak plinth you are right... but good enduse stuff is made with reusable code that has the same quality of libs and assets
You have SNS right ? @foggy vale
sns?
no I'm synching by myself
oh, nm then
just the transofrm cracker
if you have any reference I should look at just post me a link
I'm eigher to read this days
Question: Is it better to start to learn about the networking side and then code a game or do a single player game and convert it at some point to multiplayer (or any pointers to a starting point of how i can eventually achieve a working multiplayer are welcome )
depends on what u know
to convert a complicate game to multiplayer later it is like to write it from scratch
I personally would start with some of the basic HLAPI's tutorials, just to see the starting point - but be aware many of them do things very wrong
but it will give you the language you need to do better
ok - i'm kind of lost as it seems unity is between 2 different networking methods and want to get a basic understanding of what is the best one to use and where to start
actually never go low if you can... takes forever to use low level libs
for the network at least
i've created basic games in the past and want to try a multiplayer game - starting points? The manual or are there any working examples that do it properly that i can disect?
just lean from any Llapi documentation... the new unity low level lib is very similar
PUN2/Mirror/Forge/Bolt all have tutorials that are worth poking at for starters
or if you are beginning to code go for HLapi to understand things
ok and Hlapi is where unity is at or where its heading?
The risk of any HLAPI is starting to think that how they do things is the right way, which isn't the case
@jade glacier @weak plinth thanks guys for the help. I take a break now
off to breakfast here, lates
ty for the tut links Emotitron
Hi guys, I feel really stupid right now, could you help me with a very simple issue? I have just begun the PUN 2 basics tutorial, but something is wrong. When I call PhotonNetwork.JoinRandomRoom() I never get any message that the connection was successful or unsuccessful
Thats all of it.
A button calls Connect() which calls JoinRandomRoom() or ConnectUsingSettings() if the client isnt connected. The connection to the master works fine, but in OnConnectedToMaster() it should call JoinRandomRoom(). Photon should then call back either OnJoinedRoom() or OnJoinRandomFailed() but nothing happens.
Thats all I get
@jade glacier https://stackoverflow.com/questions/53785910/avoiding-the-overhead-of-c-sharp-virtual-calls
I don't make my calls using the interfaces if that is what that is about? That is why I go after the delegates for my code gen methods rather than abstract into interfaces for that. If I am reading that right at a glance. @weak plinth
No, just sending you this in general
If you are or going to use some interface to allow api stuff
yeah, for hot paths I try to avoid interfaces, but I do have a bunch in place just because it makes the coding sane
but down the line I may replace them all with cached delegates. Or not.
This software is likely going to become the property of Exit with its main use being PUN2... and outlandish numbers of net objects are just not a thing in that environment.
So sanity of code will trump microop
Definitely ๐
@weak plinth Do you have any experience with trying to get codegen to happen right at build time?
I could solve a lot of my problems if I could make that work, but as it has been, generating those files in the PreBuild hasn't been getting them added to the project... so I am missing some way to get them compiled and included in time.
AssetDatabase.Refresh();```
aren't making it happen
Or I should say they happen, the files exist and are compiled in the editor after the build... but the build is missing them.
Maybe with the buildpipeline also?
There should be a way to run code just before building
I think I tried that, but will try again since I have lost track of all of the places I have tried to stick it
It runs, I see the log in the right order
but the build doesn't get it
I haven't worked with that yet. Will have to look at it once I fiddle about with cecil
sharplab keeps giving me virtual calls
Even if I do what that stackoverflow link suggests
There is a possibility that the JIT compiles it away
But seeing JIT doesn't work on that site for me
If thisType is a reference type (as opposed to a value type) then ptr is dereferenced and passed as the 'this' pointer to the callvirt of method.
If thisType is a value type and thisType implements method then ptr is passed unmodified as the 'this' pointer to a call method instruction, for the implementation of method by thisType.
If thisType is a value type and thisType does not implement method then ptr is dereferenced, boxed, and passed as the 'this' pointer to the callvirt method instruction.```
So in case of a value type, it's a normal method call instead of a virtual
Good to know
for some reason the marked code is only executed on the host and not the clients. why is that ?
You are calling it from where? @fading apex
nm, you must be calling it on the host for that to fire at all
at a glance that should work, might need to see the whole file
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class LootController : NetworkBehaviour
{
public List<Lootable> drops = new List<Lootable>();
public GameObject crateDestrcutionParticle;
public float crateHealth;
private GameObject lootObject;
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Bullet")){
crateHealth -= collision.gameObject.GetComponent<Bullet>().GetDamage();
if (crateHealth <= 0){
CmdDestroyed();
}
}
}
[Command]
void CmdDestroyed() {
Lootable loot = drops[Random.Range(0, drops.Capacity)];
RpcSpawnParticle();
if (loot.GetChance() >= Random.Range(0.0f, 1.0f)){
lootObject = loot.GetLoot();
RpcSpawnLoot(loot);
}
}
[ClientRpc]
void RpcSpawnParticle() {
Instantiate(crateDestrcutionParticle, transform.position, Quaternion.identity);
Destroy(this.gameObject);
}
[ClientRpc]
void RpcSpawnLoot(Lootable loot){
Instantiate(lootObject, transform.position, Quaternion.identity);
}
}
that would be the whole file
its not very advanced/clean code but it works
and its the first time i am making multiplayer and only my second serious unity project
You have a debug.LogError in that RPC to make sure everything is happening where you think?
I'm not seeing a lot of checks to make sure those commands and rpcs aren't really being checked to make sure they are coming from an actual authority
You need to debug that back, and make sure for starters that that Command is firing on the server as expected, the work forward from there.
ok ill try that thx
Actually @jade glacier I'm not sure if I'm going to make that serializer anytime soon
It's going to cost me a whole lot of time
And I'm literally getting nothing of any game done
I'm just making libraries at this point
And I shouldn't be needing anything more than what I can do with Wobes' solution. Since I want my packets to be blittable exclusively (at least the ones for game state)
And I just know it's going to take me a whole bunch of time just figuring out how to do with Cecil what I can already do with reflection.emit
For something I probably won't be using anyway...
I'll finish the blittable serializer and get going again (hopefuly)
this little side project has cost me 4 days already... its definitely not a hit it and quit it LOL
Yeah, all of the guys have libraries you can steal to get up and running
and you can be a champ and just PR theirs
I spend about 1 day making serialization of non-nested classes/structs with reflection.emit
It was interesting working with IL
I've learned something
So it's all good for me to not continue with it
I'll throw the blittableserializer on git once it's finished
Using FSE's bitbuffer maybe
I'm going to go with attributes (optionally) as well like you are
To define max size
Because I know I will actually be using that a lot for my own projects
I would, this mid to low level stuff isn't interesting to anyone... so unless you have a product you are producing with it - I would put a pin in it and just refactor it next year when you are bored some weekend.
Well, it would be blazing fast serialization to raw bytes
I'm not sure if someone offers that for c# out there
Most serialization uses some custom format with overhead etc
@weak plinth you can use Cap'n'Proto, which is effectively an arena allocator
you can edit its C# client to support mutable memory, it's just not recommended, but it will work
authoring your own arena allocator is essentially reinventing DOTS
cap'n'proto has the benefit of having a cross-platform representation, which DOTS does not
the java and C++ mutations are fine, but i haven't tried it with C#
people just reinvent arena allocators all day
i don't know why it's so poorly taught / documented
its just very difficult to author something that uses region-based memory correctly
i think there are better approaches, e.g., use an embedded Lua runtime on both your client and your server, and synchronize the lua memory region
but even roblox, which had an opportunity to do this, still requires you to declare what gets replicated
so it's not clear if it's possible to make stuff any simpler than Cap'n'Proto, i.e., you will have to declare, somewhere, what memory should be treated specially. whether that's an attribute or a type definition file or a call to a method, it doesn't matter, that work will be done somewhere
i think you'd like it
Sounds convoluted to me, at least for network messaging
Simply using a Bitstream is much more efficient than what I'm reading on their website
And what I've been talking about has nothing to do with region based memory management
https://capnproto.org/encoding.html
Their encoding has a ton of overhead
I don't need any of this
They are using 8 bytes just to store some information about a struct?
If I use a bitstream, I need exactly 0 bytes to store information about a struct
Maybe 1 bit per field to determine if it should be updated or not
Hello! Anyone knows other solution like SpatialOS but completely Open Source?
Foer example: https://lucidscape.com/
I don't know of anything open source in that topic, no. I suspect the new company/product Coherence (announced a couple of months ago), which has the involvement of Unity Founder David Hagelson might be doing something similar (maybe better as well?).
My personal view is that theses are super cool and neat, but in reality (at least SpacialOS looked like) are extremely expensive from a business perspective (even open source, the server costs may skyrocket, IMHO).
But that's all I know (sorry).
@gleaming prawn Thank you very much!!
@weak plinth i think the idea is that your cap'n'proto message is also your game state, you mutate it as needed, when you need to transmit all or part of it it is already in a buffer for you
But it's senseless to use this for network messasing
Just pass blittable structs around
Smaller and faster
maybe i'm missing something, but isn't an array of bytes and a blittable struct sort of the same thing?
Definitely missing something
Structs are formatted (structured) data
An array of bytes of just data
It's pretty useless without interpretation
i think cap'n'proto's whole idea is that the interpretation is "free" in the sense that it requires no copying
Definitely not
so once you receive a buffer that represents a cap'n'proto message (really a collection of objects), you can get at those objects without copying into your runtime
That's not how it works
Everytime you read data from your buffer, you are copying
If you don't format it, but rather format it on the go
so why do they say on their site,
"This benchmark is, of course, unfair. It is only measuring the time to encode and decode a message in memory. Capโn Proto gets a perfect score because there is no encoding/decoding step."
what could they mean by that
Because encoding is not the same as copying memory?
Google encoding, it's something completely different
what could they mean by decoding being free
so how could that work
i receive a byte buffer, and i cast it into a struct, for example
in C, for example
did i copy?
Yes
maybe the more appropriate term woudl be a union
i have a type that is a union of a byte buffer and a bunch of ordered, name fields
is that a copy?
Still yes
is there some term for
Since your network layer doesn't recieve unioned structs
You have to copy it into your unioned struct first
i'm aware you can't avoid copying from your network stack
But don't ever do that
into your runtime
Because it's bad
but once i have the byte buffer from my network stack
can't i just interpret it, in a special way, where i no longer copy the bytes?
No
hmm
You need to copy from the heap to the stack everytime you access
Or you convert pointers
But then you need to pin the array
And it loses all formatting
i understand from the point of view of assigning primtive variables, there will be a copy
Any value there will be
Struct layout is not guaranteerd to be the same on every system
Nor even on a restart/rebuild of your application
what if there was a way to guarantee a struct layout
So just copying an entire struct is very dangerous
do you think that would be useful?
Different types can be different sizes depending on architecture
You could copy directly to a struct
Rather than reading value per value into one
so copy directly to a struct, that sounds valuable right?
and the main problem is, actually, a canonical representation of structs, across process and runtime boundaries
i guess what i'm getting at is, cap'n'proto (and really protobufs), they sort of do these things. ECS is another idea for doing this thing
where actually, the thing that is a huge chore is gathering up all your little game structs and writing them out
I'm sure they do exactly the same as a bitstream
it's a much bigger chore to write value by value, and then read value by value
But rather have the data order embedded in the protocol
Instead of having strictly user-defined structs that you read top to bottom
i guess you should take a look at what it does, technologically, i think you would like it ๐
I think you are looking for some magic solution that doesn't exist
it just provides some hints as to why ECS looks the way it does, like what they are learning from
That has no relation to network serialization
i think the idea is, they want to provide a general networking framework
something akin to the SC2/Overwatch engine's way of handling things
which is having a sort of runtime, and its associated memory, and working with the memory in an efficient way
a "game runtime"
that is itself embedded into a normal C# runtime
most of that fun stuff in Overwatch isn't so much about the networking, that is more about the ability to resim quickly
FYI, ECS has nothing to do with networking explicitely
well, surely you'd agree that if you can ship pieces of data to identical runtimes, but one happens to be client and the other server
and sort of "work on" that data
it would make resimming, as a general idea, easier
for normals like us
networking poops on all of that for the most part, since you want to NOT have all of those nice structures for the network. You bitpack, compress and mangle the data any way possible to fit it through the pipes.
Yes, that's why you serializa and deserialize from a bitstream the same way on both ends
the web development community used to call this "isomorphic" but i think they had a different name for it