Lightweight toolset for creating concurrent networking systems for multiplayer games.
NetStack is self-contained and has no dependencies.
Modules:
- Buffers
- Thread-safe array pool
- Quantization
- Half precision algorithm
- Bounded range algorithm
- Smallest three algorithm
- Serialization
- Lightweight and straightforward
- Fast processing
- Span support
- Fluent builder support
- Compact bit-packing
- ZigZag encoding
- Variable-length encoding
- Threading
- Array queue
- Single-producer single-consumer first-in-first-out non-blocking queue
- Concurrent buffer
- Multi-producer multi-consumer first-in-first-out non-blocking queue
- Concurrent pool
- Self-stabilizing semi-lockless circular buffer
- Array queue
- Unsafe
- Fast memory copying
By default, all scripts are compiled for .NET Framework 3.5. Define NET_4_6 directive to build the assembly for .NET Framework 4.6 or higher. Define NET_STANDARD_2_0 to build the assembly for .NET Core 2.1 or higher.
Define NETSTACK_SPAN to enable support for Span.
Define NETSTACK_BUFFERS_LOG to enable buffers logging.
// Create a new array pool with a maximum size of 1024 bytes per array, 50 arrays per bucketArrayPool<byte>buffers=ArrayPool<byte>.Create(1024,50);// Rent buffer from the pool with a minimum size of 64 bytes, the returned buffer might be largerbyte[]buffer=buffers.Rent(64);// Do some stuffbytedata=0;for(inti=0;i<buffer.Length;i++){buffer[i]=data++;}// Return buffer back to the poolbuffers.Return(buffer);// Define a message objectclassMessageObject{publicuintid;publicbyte[]data;}// Create a new objects pool with 8 objects in the headConcurrentPoolmessages=newConcurrentPool<MessageObject>(8,()=>newMessageObject());// Acquire an object in the poolMessageObjectmessage=messages.Acquire();// Do some stuffmessage.id=1;message.data=buffers.Rent(64);bytedata=0;for(inti=0;i<buffer.Length;i++){message.data[i]=data++;}buffers.Return(message.data);// Release pooled objectmessages.Release(message);// Create a new concurrent buffer limited to 8192 cellsConcurrentBufferconveyor=newConcurrentBuffer(8192);// Enqueue an objectconveyor.Enqueue(message);// Dequeue objectMessageObjectmessage=(MessageObject)conveyor.Dequeue();ushortquantizedSpeed=HalfPrecision.Quantize(speed);floatspeed=HalfPrecision.Dequantize(quantizedSpeed);// Create a new BoundedRange array for Vector3 position, each entry has bounds and precisionBoundedRange[]worldBounds=newBoundedRange[3];worldBounds[0]=newBoundedRange(-50f,50f,0.05f);// X axisworldBounds[1]=newBoundedRange(0f,25f,0.05f);// Y axisworldBounds[2]=newBoundedRange(-50f,50f,0.05f);// Z axis// Quantize position data ready for compact bit-packing QuantizedVector3quantizedPosition=BoundedRange.Quantize(position,worldBounds);// Read quantized dataConsole.WriteLine("Quantized position - X: "+quantizedPosition.x+", Y:"+quantizedPosition.y+", Z:"+quantizedPosition.z);// Dequantize position data ready for reconstruction after bit-packingVector3dequantizedPosition=BoundedRange.Dequantize(quantizedPosition,worldBounds);// Quantize rotation data ready for compact bit-packing QuantizedQuaternionquantizedRotation=SmallestThree.Quantize(rotation);// Read quantized dataConsole.WriteLine("Quantized rotation - M: "+quantizedRotation.m+", A:"+quantizedRotation.a+", B:"+quantizedRotation.b+", C:"+quantizedRotation.c);// Dequantize rotation data ready for reconstruction after bit-packingQuaternionrotation=SmallestThree.Dequantize(quantizedRotation);// Create a new bit buffer with 1024 chunks, the buffer can grow automatically if requiredBitBufferdata=newBitBuffer(1024);// Fill bit buffer and serialize data to a byte arraydata.AddUInt(peer).AddString(name).AddBool(accelerated).AddUShort(speed).AddUInt(quantizedPosition.x).AddUInt(quantizedPosition.y).AddUInt(quantizedPosition.z).AddUInt(quantizedRotation.m).AddUInt(quantizedRotation.a).AddUInt(quantizedRotation.b).AddUInt(quantizedRotation.c).ToArray(buffer);// Get a length of actual data in bit buffer for sending through the networkConsole.WriteLine("Data length: "+data.Length);// Reset bit buffer for further reusingdata.Clear();// Deserialize data from a byte arraydata.FromArray(buffer,length);// Unload bit buffer in the same orderuintpeer=data.ReadUInt();stringname=data.ReadString();boolaccelerated=data.ReadBool();ushortspeed=data.ReadUShort();QuantizedVector3position=newQuantizedVector3(data.ReadUInt(),data.ReadUInt(),data.ReadUInt());QuantizedQuaternionrotation=newQuantizedQuaternion(data.ReadUInt(),data.ReadUInt(),data.ReadUInt(),data.ReadUInt());// Check if bit buffer is fully unloadedConsole.WriteLine("Bit buffer is empty: "+data.IsFinished);/*Bits Min Dec Max Dec Max Hex Bytes Used0-7 0 127 0x0000007F 1 byte8-14 128 1023 0x00003FFF 2 bytes15-21 1024 2097151 0x001FFFFF 3 bytes22-28 2097152 268435455 0x0FFFFFFF 4 bytes29-32 268435456 4294967295 0xFFFFFFFF 5 bytes*/data.Add(9,256);uintvalue=data.Read(9);// Create a one-time allocation buffer poolstaticclassBufferPool{[ThreadStatic]privatestaticBitBufferbitBuffer;publicstaticBitBufferGetBitBuffer(){if(bitBuffer==null)bitBuffer=newBitBuffer(1024);returnbitBuffer;}}// Define a networking messagestructMessageObject{publicconstushortid=1;// Used to identify the message, can be packed or sent as packet headerpublicuintpeer;publicbyterace;publicushortskin;publicvoidSerialize(refSpan<byte>packet){BitBufferdata=BufferPool.GetBitBuffer();data.AddUInt(peer).AddByte(race).AddUShort(skin).ToSpan(refpacket);data.Clear();}publicvoidDeserialize(refReadOnlySpan<byte>packet,intlength){BitBufferdata=BufferPool.GetBitBuffer();data.FromSpan(refpacket,length);peer=data.ReadUInt();race=data.ReadByte();skin=data.ReadUShort();data.Clear();}}