2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00
2026-03-19 14:52:12 +03:00

arpack logo

ArPack

Binary serialization code generator for Go and C#. Define messages once as Go structs — get zero-allocation Marshal/Unmarshal for Go and unsafe pointer-based Serialize/Deserialize for C#.

Built for game networking where every byte and allocation matters.

Features

  • Single source of truth — define messages in Go, generate both Go and C# code
  • Float quantization — compress float32/float64 to 8 or 16 bits with a pack struct tag
  • Boolean packing — consecutive bool fields are packed into single bytes (up to 8 per byte)
  • Enumstype Opcode uint16 + const block becomes a C# enum
  • Nested types, fixed arrays, slices — full support for complex message structures
  • Cross-language binary compatibility — Go and C# produce identical wire formats

Installation

go install edmand46/apack@latest

Usage

arpack -in messages.go -out-go ./gen -out-cs ../Unity/Assets/Scripts
Flag Description
-in Input Go file with struct definitions (required)
-out-go Output directory for generated Go code
-out-cs Output directory for generated C# code
-cs-namespace C# namespace (default: Arpack.Messages)

At least one of -out-go or -out-cs is required.

Output files:

  • Go: {name}_gen.go
  • C#: {Name}.gen.cs

Schema Definition

Messages are defined as Go structs in a single .go file:

package messages

// Quantized 3D vector — 6 bytes instead of 12
type Vector3 struct {
    X float32 `pack:"min=-500,max=500,bits=16"`
    Y float32 `pack:"min=-500,max=500,bits=16"`
    Z float32 `pack:"min=-500,max=500,bits=16"`
}

// Enum
type Opcode uint16

const (
    OpcodeUnknown   Opcode = iota
    OpcodeAuthorize
    OpcodeJoinRoom
)

type MoveMessage struct {
    Position  Vector3    // nested type
    Velocity  [3]float32 // fixed-length array
    Waypoints []Vector3  // variable-length slice
    PlayerID  uint32
    Active    bool       // 3 consecutive bools →
    Visible   bool       //   packed into 1 byte
    Ghost     bool
    Name      string
}

Supported Types

Type Wire Size
bool (packed) 1 bit (up to 8 per byte)
int8, uint8 1 byte
int16, uint16 2 bytes
int32, uint32, float32 4 bytes
int64, uint64, float64 8 bytes
string 2-byte length prefix + UTF-8
[N]T N × sizeof(T)
[]T 2-byte length prefix + N × sizeof(T)

Float Quantization

Use the pack struct tag to compress floats:

X float32 `pack:"min=-500,max=500,bits=16"`  // 2 bytes instead of 4
Y float32 `pack:"min=0,max=1,bits=8"`        // 1 byte instead of 4
Parameter Description
min Minimum expected value
max Maximum expected value
bits Target size: 8 (uint8) or 16 (uint16)

Values are linearly mapped: encoded = (value - min) / (max - min) * maxUint.

Generated Code

Go

func (m *MoveMessage) Marshal(buf []byte) []byte
func (m *MoveMessage) Unmarshal(data []byte) (int, error)

Marshal appends to the buffer and returns it. Unmarshal reads from the buffer and returns bytes consumed.

C#

public unsafe int Serialize(byte* buffer)
public static unsafe int Deserialize(byte* buffer, out MoveMessage msg)

Uses unsafe pointers for zero-copy serialization. Returns bytes written/consumed.

Wire Format

  • Little-endian byte order
  • No message framing — fields are written in declaration order
  • Variable-length fields (string, []T) prefixed with uint16 length
  • Booleans packed as bitfields (LSB first, up to 8 per byte)
  • Quantized floats stored as uint8 or uint16

Running Tests

# Unit tests (parser + generator)
go test ./tools/arpack/...

# End-to-end cross-language tests (requires dotnet SDK)
go test ./tools/arpack/e2e/...
S
Description
No description provided
Readme MIT 685 KiB
Languages
Go 74.3%
C# 25%
Makefile 0.7%