pulsenet

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2025 License: AGPL-3.0 Imports: 5 Imported by: 2

README

Pulse Networking

Pulse Networking

Because reliable multiplayer shouldn't be a fantasy

FeaturesArchitectureWho is this for?InstallationUsageTestingContributionsLicense

PulseNet

PulseNet is a high-performance, low-level networking loop built around the ENet protocol, written in Go. It's designed for real-time applications that need reliable and unreliable messaging with minimal latency — like games, simulations, or your home-brew distributed system that definitely doesn't violate every networking best practice.

This is not some plug-and-play “framework” with 200 transitive dependencies. It gives you the raw tools you need: a clean event-driven networking loop, tight control over peer connections, and message handling built with predictable performance in mind. You get to keep your hand on the wheel. No magic. No surprises.

Features

  • ✅ Event-driven architecture (connect, disconnect, packet received)
  • ✅ Reliable and unreliable packet support (thanks to ENet)
  • ✅ Send to individual peers or broadcast
  • ✅ Mockable abstraction layer for full test coverage (92% and climbing)
  • ✅ Unit tested and race-free (actually race-free, not “Go test says it’s fine”)
  • ✅ Built-in ENet initialization/deinitialization
  • ✅ Commercial-license unfriendly (read on)

Architecture

PulseNet wraps ENet’s event-based UDP system in a Go-native structure that gives you full control without diving into unsafe C bindings. Here’s how it works:

Core Components
  • NetworkLoop

Your main loop. Internally spins up a goroutine to pull events from ENet’s Service() method and pushes them onto a buffered channel. You control when to flush them with Tick().

  • NetEventHandler

A consumer-provided interface that receives NetEvent structs. Implement this once, and you’re ready to handle connects, disconnects, and packets.

  • SendTo / Broadcast

Queue up outbound messages by peer and flush them manually during Tick(). Predictable. Deterministic. No black boxes.

  • Abstraction Layer

Everything talking to ENet goes through interfaces: enetHost, enetPacket, enetPeer, and enetEvent. Why? So we can test without spinning up real sockets like amateurs.

Event Flow
  1. networkLoop.eventLoop() runs in a goroutine, pulling events from ENet.
  2. Events are translated into NetEvent and pushed onto a buffered channel.
  3. Your code calls Tick(), which:
    • Flushes queued events to your handler.
    • Flushes outbound messages from SendTo/Broadcast to actual peers.
Thread Safety

All public methods are goroutine-safe, and internal maps (like peers, sendQueues) are protected via a sync.Mutex. There’s no global state, and nothing leaks.

Initialization

ENet’s Initialize()/Deinitialize() lifecycle is wrapped automatically when using NewNetworkLoop(). Tests use a custom newNetworkLoop() with mocks — no actual ENet usage required.

Who is this for?

Anyone building:

  • Realtime multiplayer games
  • Networked simulations
  • Systems that need high-performance UDP messaging
  • Projects where “latency” means something and “REST APIs” are a curse word

If you're looking for JSON over HTTP, just stop reading. Seriously.

If you're looking for tight control over bytes on the wire and don't want to write C — welcome.

License

PulseNet is released under the GNU Affero General Public License v3.0 (AGPLv3). If you don't know what that means, here's the blunt version:

If you use this in anything other than your personal hobby project, you need to release your source code too.

Running it as a backend service? Still AGPLv3. Hosting it on a server? AGPLv3. Embedding it into a proprietary application? You guessed it — AGPLv3.

If this makes you uncomfortable, good. That's the point. But hey, if you're one of the rare unicorns who wants a commercial license, we can talk.

Installation

go get git.pulsenet.dev/pulse/pulsenet

Usage

Create a loop:

loop, err := pulsenet.NewNetworkLoop("0.0.0.0", 12345, 16)
if err != nil {
	log.Fatalf("Failed to start loop: %v", err)
}
defer loop.Destroy()

Register a handler:

loop.RegisterEventHandler(myHandler)

Run your loop:

for {
	loop.Tick()
}

Send messages:

loop.SendTo(peerID, channelID, pulsenet.FlagReliable, []byte("hi"))

Testing

PulseNet has a full suite of unit tests using mocked ENet interfaces. We don’t test around the networking — we punch right through it. You can too.

go test -v -race -cover

Expect >90% coverage, no races, and no excuses.

Contributions

Patches welcome. Dumb ideas aren’t.

PRs should:

  • Improve performance
  • Improve testability
  • Reduce complexity
  • Not break the damn API
  • Include a signed

Do not send:

  • A gRPC wrapper
  • A JSON marshaling layer
  • Your weird React frontend

By submitting code to this project, you agree to license your contribution under the same license as the project: AGPLv3.

If you're submitting a significant contribution, you may be asked to sign a Contributor License Agreement (CLA).

Final Note

This library is not here to babysit you. If you don’t understand the difference between TCP and UDP, or why packet ordering matters, go read a book before filing issues.

If you do know what you’re doing — PulseNet gives you the tools to do it right.

Enjoy.

Documentation

Overview

Package pulsenet provides a high-performance, event-driven networking loop built on top of the ENet protocol. It is designed for real-time applications that require low-latency messaging, reliable and unreliable packet delivery, and tight control over peer-to-peer communication.

This package exposes a consistent and testable interface for sending and receiving packets over the network using ENet semantics. It is suitable for multiplayer games, simulations, and distributed systems where predictable performance is critical.

Usage:

server, err := pulsenet.NewNetworkLoop("0.0.0.0", 12345, 16)
if err != nil {
  log.Fatal(err)
}
defer server.Destroy()

server.RegisterEventHandler(myHandler)

for {
  server.Tick()
}

AGPLv3 Licensed. Commercial licenses available only if you lost a bet.

mocks_enet.go

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type EventType

type EventType int

EventType describes the type of a networking event.

Possible values: - EventPacketReceived: A packet was received from a peer. - EventConnected: A peer connected. - EventDisconnected: A peer disconnected.

const (
	EventPacketReceived EventType = iota
	EventConnected
	EventDisconnected
)

type NetEvent

type NetEvent struct {
	Type    EventType
	PeerID  uint32
	Opcode  uint16 // Only valid for EventPacketReceived
	Payload []byte // Only valid for EventPacketReceived
}

NetEvent is the internal event representation used by PulseNet. It is delivered to the NetEventHandler you register.

type NetEventHandler

type NetEventHandler interface {
	HandleEvent(NetEvent)
}

NetEventHandler is implemented by any object that wants to receive networking events. This is the only integration point for receiving packets or connection events.

type NetworkLoop

type NetworkLoop interface {
	Tick() int
	SendTo(peerID uint32, channelID uint8, flags PacketFlag, data []byte)
	Broadcast(channelID uint8, flags PacketFlag, data []byte)
	RegisterEventHandler(handler NetEventHandler)
	Destroy()
}

NetworkLoop defines the high-level interface for PulseNet's networking system. It provides methods to send messages, receive events, and manage the lifecycle of the networking loop.

Users should call Tick() periodically (e.g. every frame or tick) to process events and flush messages. Always call Destroy() before shutdown to clean up internal resources.

func NewClientLoop added in v1.0.1

func NewClientLoop(hostname string, port uint16) (NetworkLoop, error)

NewClientLoop creates a NetworkLoop and connects it to a remote server using ENet. It automatically initializes the ENet library if needed and starts the internal event loop. You must call Destroy() when done to clean up resources.

Example:

loop, err := pulsenet.NewClientLoop("127.0.0.1", 12345)
if err != nil {
  log.Fatal(err)
}
defer loop.Destroy()

Note: If you run both server and client in the same process, ENet will only be initialized once. But it's your responsibility to avoid calling Destroy on both sides if you plan to reuse ENet across instances.

func NewNetworkLoop

func NewNetworkLoop(addr string, port uint16, maxPeers uint64) (NetworkLoop, error)

NewNetworkLoop initializes ENet, creates a host, and returns a running network loop.

addr: Address to bind to (e.g. "0.0.0.0"). port: Port to bind to. maxPeers: Maximum number of simultaneous connections.

The returned loop must have Destroy() called on it to clean up the host and ENet state.

Returns an error if the host cannot be created.

type PacketFlag

type PacketFlag uint32

PacketFlag wraps ENet's packet flags and allows the user to specify reliability, sequencing, and other behavior when sending messages.

const (
	FlagReliable           PacketFlag = PacketFlag(enet.PacketFlagReliable)
	FlagUnsequenced        PacketFlag = PacketFlag(enet.PacketFlagUnsequenced)
	FlagNoAllocate         PacketFlag = PacketFlag(enet.PacketFlagNoAllocate)
	FlagUnreliableFragment PacketFlag = PacketFlag(enet.PacketFlagUnreliableFragment)
	FlagSent               PacketFlag = PacketFlag(enet.PacketFlagSent)
)

Source Files

  • doc.go
  • enet_abstractions.go
  • enet_init.go
  • enet_mocks.go
  • enet_wrappers.go
  • eventloop.go
  • events.go
  • handlers.go
  • netloop.go
  • packets.go
  • send.go
  • tick.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL