#ETA for a netcode utility package without the ghost stuff ?
1 messages · Page 1 of 1 (latest)
Netcode without ghosts is just the transport package
Unity.Transport is what you need
Netcode is based on that
yeah but there is no implementation of commands like the RPC commands are doing
you'll have to make your own
I don't like the way rpcs work in netcode
you need structural changes
so I just wrote my own which is reading from dynamic buffer of bytes
Do you have pointers on that ?
cause it's been two days i'm struggling on that topic ^^
Is it something you can share ?
no
FML ... ^^"
- make some
ReadRpc,WriteRpcdynamic buffers with only one field ofbyte
- make generic method that writes your rpc structs into it
- make a generic method that is able to read from those buffers rpc of your generic type
that's how mine works
so it looks like rpcBuffer.WriteRpc(new SomeRpcStruct{ someField = 123 } );
and reading looks like bool hasRpc = rpcBuffer.TryReadRpc(out SomeRpcStruct rpcData);
how do you know what type of rpc is being sent ?
I don't
I just check buffer for it
if it has one
internally that generic method just goes over all rpcs in a buffer
reads their header
compares typeHash to generic one
typeHash is obtained via BurstRuntime utility method
typeHash -> gotcha you have a type hash to identify what data is coming
lemme check that method
over network though I send stableTypeHash
to identify rpc type
it's a long, but it's a ready thing
you can get it from entities type manager
TypeManager.GetTypeInfo().StableTypeHash);
this ?
and other question when you talk about dynamicBuffer You mean the type in entities ? or Something else ?
Yeah, it's easy way to get networkable type hash
Yeah
Technically it doesn't matter where you push rpc
I just prefer to have this buffer on connection entitu
These buffers*
wtf it's a pain in the ass to do something that is scalable on that side ...
my god
writing your own networking is a pretty big undertaking
that the thing ... I don't want to
If I could usesomething like netcode for entities With the basics already implementated that would be perfect
@north crescent coming back to you, thanks for the hash idea, it's funny cause when digging into the source code for netcode for entities
Turns out that's the same method they are using with their IRpc commands
and it work like a charm
a new question arise tho. what is your solution doing differently than RPC in netcode for entities ?
cause you said you are using dynamicbuffer of bytes, but that's the same thing they do with a few extra steps
they expose it as components
I don't like that
basically caps everything to main thread
my implementation is fully threaded
and will scale as long as you have free thread
Okey, make sense.
Cause i've been looking into their OutgoingRpcDataStreamBuffer and IncomingRpcDataStreamBuffer and i was wondering what was diferrent.
Theirs is per connections, your is , per commands ?
per connection
each command is polled via generic method
so basically you can have 50 jobs reading same buffer
with different methods like buf.TryRead<MyRpcType>
it's just that their buffers are internal
instead they want you to rely on entities getting created per RPC
yeah, you create an entitie with a rpc command attached to it, then a RpcSystem is gathering & sending them on the network , right ?
Your implementation don't rely on these said entities
smth like that, i don't remember details tbf
yeah
in my case you just query that buffer and call generic method buf.Write<MyRpctype>
we have this buffer cloned on actual "character" entity of player
so it's easy to actually look it up
Do that mean you have 1 buffer per command type ?
and then all buffer internals just gets copied to connection entity
no, it's one and only buffer
I just write different bytes to it
and magically it gets sent to client
😅
Okey yeah , because you always start a message by the hash of the command you always know what packet is coming next
Cleaver
Do you have codegen too to create the boiler plate for the commands ?
or is it an another methods you are using ?
Like for automatic registration & stuff
it's generic
there's no boilerplate
it's a combination of reflection and generics, no codegen
I was thinking , instead of relying on their codegen for my command I could just have an initial system looking in the assembly for stuff implementing a INetworkCommand Interface and registring it in a hasmap somewhere
yeah, that's exactly what I do
Okey that would implify a lot
you can even make it "constant"
and staticly available
like TypeManager
just need to look how TypeManager functions
to make it burst compatible
just don't forget that it needs very special cleanup treatment
so do look at how TypeManager disposes it's own native collections on domain reload
I'll look into it.
Last question and the biggest hurdle i'm facing currently.
How to handle the automatic reception / sending of these commands ?
Like as I stated before netcodeEntities use a lot of functions & systems to take care of that. I feel like it's overcomplicated.
What does your pipeline look like ?
just have a system that queriees those buffers and sends them?
I didn't look at netcode of entities
so it was simple to me
😅
probably shot myself in the foot by looking into that first
FML
Alright so to get my thoughts wrapped up.
I'm thinking :
public interface INetworkCommand : IComponentData { }
ISerializableCommand where T : struct, INetworkCommand {
public void Serialize(ref DataStreamWriter writer, in T data);
public void Deserialize(ref DataStreamReader reader,ref T data);
}
This would be the base structure.
Then I would have a System taking care of the registration with assembly searching & stuff ( easy to do )
This would create a list of :
public struct NetworkCommandData : IComparable<NetworkCommandData>{
public ulong TypeHash;
public NetworkPortableFunctionPointer<CreateDelegate> Deserialize;
public NetworkPortableFunctionPointer<CreateDelegate> Serialize;
public int CompareTo(NetworkCommandData other){}
}
Holding delegates for both serialization & deserialization pointers
public struct NetworkPortableFunctionPointer<T> where T : Delegate
{
public NetworkPortableFunctionPointer(T executeDelegate) { Ptr = BurstCompiler.CompileFunctionPointer(executeDelegate); }
public readonly FunctionPointer<T> Ptr;
}
just a copy of netcode wrapper for function pointer, very handy.
The collection would look something like this :
public struct NetworkCommandCollection
{
public NativeParallelHashMap<ulong, int> TypeHashToIndex;
public NativeList<NetworkCommandData> NetworkCommands;
}
With create delegate functions & stuff that goes with it.
The command collecting system will then put that as a DB in a shared static for later usage.
Then the question is how to manage the buffers to write & read from ... & how to actually generically read and write on
Make sense so far ?
you won't have generics when serializing to data stream writer
unless you literally expose it (which is a bad thing)
generics only exist on high level
when you are making a packet, you only have bytes at this point
so you just need to figure how to convert those bytes into networkable bytes
and then vice versa
yup I know, that why you include the haslong first in the packet to know whay you are dealing with, right ?
stable hash is only used in networked packet
and that hy there is a hashmap in the first place ^^ to match these bytes to their correct command ?
outside of network packet
I use burst type hash
hashmap is to serialize/deserialize into/from network packets
your write buffer goal is to just let user write commands easily to it
your read buffer goal is to just let user read commands easily from it
the rest is handled internally
DynamicBuffer<byte> buffer = new DynamicBuffer<byte>();
NativeArray<byte> bufferConverted = new(buffer.Length, Allocator.Temp);
writer.WriteBytes(bufferConverted);
buffer being where you write the commands
And you send the buffer with something like that I suppose ?
writer being DataStreamWriter writer
I might be overthinking at this point xD
And you get the bytes from
int streamLength = stream.Length;
NativeArray<byte> streamData = new(streamLength, Allocator.Temp);
stream.ReadBytes(streamData);
& then read from streamData
seeing that it's a copy you can read that multiples time , gather all the command in the stream and read them in a parrallel jobs if needed
runtime buffer contains runtime hashes
they cannot be networked