Skip to content

Latest commit

History

124 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

alt logo

Lightweight toolset for creating concurrent networking systems for multiplayer games.

NetStack is self-contained and has no dependencies.

Modules:

  • Buffers
  • Quantization
  • Serialization
  • 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
  • Unsafe
    • Fast memory copying

Building

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.

Usage

Thread-safe buffers pool
// 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);
Concurrent objects pool
// 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);
Concurrent objects buffer
// Create a new concurrent buffer limited to 8192 cellsConcurrentBufferconveyor=newConcurrentBuffer(8192);// Enqueue an objectconveyor.Enqueue(message);// Dequeue objectMessageObjectmessage=(MessageObject)conveyor.Dequeue();
Quantize float
ushortquantizedSpeed=HalfPrecision.Quantize(speed);floatspeed=HalfPrecision.Dequantize(quantizedSpeed);
Quantize vector
// 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 quaternion
// 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);
Serialize/deserialize data
// 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);
Bit-level operations
/*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);
Abstract data serialization with Span
// 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();}}

About

Lightweight toolset for creating concurrent networking systems for multiplayer games

Topics

Resources

Stars

324 stars

Watchers

21 watching

Forks

Contributors

Languages