micro

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2025 License: LGPL-3.0 Imports: 35 Imported by: 0

Documentation

Overview

Package micro provides distributed shard management functionality for the ECS framework. It implements a leader-follower model with deterministic replay for maintaining consistency across distributed game instances.

Index

Constants

View Source
const (
	RealmUnspecified = microv1.ServiceAddress_REALM_UNSPECIFIED // Should not be used
	RealmInternal    = microv1.ServiceAddress_REALM_INTERNAL    // Reserved for internal services
	RealmWorld       = microv1.ServiceAddress_REALM_WORLD       // Publicly accessible services that are

)
View Source
const MinEpochFrequency = 10

Variables

View Source
var (
	ErrFailedToConnect   = eris.New("failed to connect to NATS server")
	ErrFailedToSubscribe = eris.New("failed to subscribe to subject")
	ErrFailedToPublish   = eris.New("failed to publish message")
	ErrInvalidConfig     = eris.New("invalid NATS configuration")
)

Common errors that can occur during NATS operations.

View Source
var (
	ErrEndpointAlreadyExists = eris.New("endpoint already exists")
)
View Source
var (
	ErrInvalidAddress = eris.New("invalid service address format")
)

Functions

func Endpoint

func Endpoint(s *ServiceAddress, endpoint string) string

Endpoint returns the full service address including the endpoint.

func HandleNATSMessage

func HandleNATSMessage(
	ctx context.Context,
	msg *nats.Msg,
	handler Handler,
	serviceAddr *microv1.ServiceAddress,
	tracer trace.Tracer,
	logger zerolog.Logger,
) ([]byte, error)

HandleNATSMessage converts a NATS message to a Request, calls the handler, and converts the Response back to bytes for NATS. This is used internally by the Service.

func RegisterCommand

func RegisterCommand[T ShardCommand](s *Shard) error

RegisterCommand registers a command type T with the shard's command manager. This allows the shard to receive and process commands of the specified type.

func String

func String(s *ServiceAddress) string

String returns the string representation of a ServiceAddress without the endpoint.

Types

type AuthInfo

type AuthInfo struct {
	Mode          iscv1.AuthInfo_AuthMode
	SignerAddress []byte // Signer's public key address (32 bytes)
}

type Channel

type Channel[T ShardCommand] chan Command

Channel is a generic buffered channel for handling commands of a specific type. It implements the commandChannel interface and provides type-safe command processing.

type Client

type Client struct {
	*nats.Conn
	// contains filtered or unexported fields
}

Client represents a NATS client with enhanced logging and error handling.

func NewClient

func NewClient(opts ...ClientOption) (*Client, error)

NewClient creates a new NATS client with the given configuration. It handles connection setup, error handling, and logging.

func NewTestClient

func NewTestClient(natsURL string) (*Client, error)

NewTestClient creates a NATS client specifically for testing without authentication. This should only be used in test environments with unauthenticated NATS servers.

func (*Client) Close

func (c *Client) Close()

Close gracefully closes the NATS connection and logs the event.

func (*Client) Request

func (c *Client) Request(
	ctx context.Context,
	address *ServiceAddress,
	endpoint string,
	payload proto.Message,
) (*microv1.Response, error)

Request sends a request to a specific endpoint and returns the response. Use this method when you want a request-reply pattern.

type ClientOption

type ClientOption func(*Client)

ClientOption defines a function that can modify a Client.

func WithLogger

func WithLogger(log zerolog.Logger) ClientOption

WithLogger returns a ClientOption that sets the logger.

func WithNATSConfig

func WithNATSConfig(cfg NATSConfig) ClientOption

WithNATSConfig returns a ClientOption that sets the NATS configuration.

type Command

type Command struct {
	Signature    []byte // Ed25519 signature (64 bytes)
	AuthInfo     AuthInfo
	CommandBytes []byte     // Serialized command bytes
	Command      CommandRaw // Parsed command bytes
}

Command represents a signed command from a player or external system.

type CommandBody

type CommandBody struct {
	Name    string          // The command name
	Address *ServiceAddress // Service address this command is sent to
	Persona string          // Sender's persona
	Payload any             // The comand payload itself
}

CommandBody contains the core command payload.

type CommandRaw

type CommandRaw struct {
	Timestamp time.Time   // When the command was created
	Salt      []byte      // Salt for additional uniqueness
	Body      CommandBody // The actual command payload and metadata
}

CommandRaw contains the actual command type.

type Handler

type Handler func(ctx context.Context, req *Request) *Response

Handler defines the signature for all service endpoint handlers. It guarantees that all responses follow the protobuf schema defined in response.proto.

type JetStreamSnapshotStorage

type JetStreamSnapshotStorage struct {
	// contains filtered or unexported fields
}

JetStreamSnapshotStorage implements SnapshotStorage using NATS JetStream ObjectStore.

func NewJetStreamSnapshotStorage

func NewJetStreamSnapshotStorage(opts JetStreamSnapshotStorageOptions) (*JetStreamSnapshotStorage, error)

NewJetStreamSnapshotStorage creates a new JetStream ObjectStore-based snapshot storage. It creates its own NATS client using the default configuration from environment variables.

func (*JetStreamSnapshotStorage) Exists

func (j *JetStreamSnapshotStorage) Exists() bool

func (*JetStreamSnapshotStorage) Load

func (j *JetStreamSnapshotStorage) Load() (*Snapshot, error)

func (*JetStreamSnapshotStorage) Store

func (j *JetStreamSnapshotStorage) Store(snapshot *Snapshot) error

type JetStreamSnapshotStorageOptions

type JetStreamSnapshotStorageOptions struct {
	ObjectName string `env:"SHARD_SNAPSHOT_JETSTREAM_OBJECT_NAME" envDefault:"snapshot"`
	// Maximum bytes for snapshot storage (ObjectStore). // Required by some NATS providers like Synadia Cloud.
	SnapshotStorageMaxBytes uint64 `env:"SHARD_SNAPSHOT_STORAGE_MAX_BYTES" envDefault:"0"`
	// contains filtered or unexported fields
}

type NATSConfig

type NATSConfig struct {
	Name            string `env:"NATS_NAME" envDefault:"isc"`
	URL             string `env:"NATS_URL" envDefault:"nats://nats:4222"`
	CredentialsFile string `env:"NATS_CREDENTIALS_FILE"`
}

NATSConfig holds the configuration for the NATS client.

func (NATSConfig) Validate

func (cfg NATSConfig) Validate() error

Validate validates the NATS configuration and returns an error if invalid.

type NopSnapshotStorage

type NopSnapshotStorage struct{}

NopSnapshotStorage is a no-op implementation of SnapshotStorage. It's used when snapshots are not needed (e.g., development, testing).

func NewNopSnapshotStorage

func NewNopSnapshotStorage() *NopSnapshotStorage

NewNopSnapshotStorage creates a new no-op snapshot storage.

func (*NopSnapshotStorage) Exists

func (n *NopSnapshotStorage) Exists() bool

func (*NopSnapshotStorage) Load

func (n *NopSnapshotStorage) Load() (*Snapshot, error)

func (*NopSnapshotStorage) Store

func (n *NopSnapshotStorage) Store(_ *Snapshot) error

type PersonaDetails

type PersonaDetails struct {
	Signers [][]byte // List of authorized signer addresses for this persona
}

PersonaDetails contains the details of a persona including its authorized signers.

func (*PersonaDetails) Marshal

func (p *PersonaDetails) Marshal() ([]byte, error)

Marshal marshals PersonaDetails to JSON bytes.

func (*PersonaDetails) Unmarshal

func (p *PersonaDetails) Unmarshal(data []byte) error

Unmarshal unmarshals JSON bytes into PersonaDetails.

type Realm

Realm represents the access scope of the service.

type Request

type Request struct {
	// Raw is the original NATS message.
	Raw *nats.Msg

	// RequestID is extracted from the request if available.
	RequestID string

	// ServiceAddress contains the service address information.
	ServiceAddress *microv1.ServiceAddress

	// Payload contains the decoded request payload, if any.
	Payload *anypb.Any
}

Request represents an incoming service request with its metadata.

type Response

type Response struct {
	// RequestID is copied from the request.
	RequestID string

	// ServiceAddress contains the service address information.
	ServiceAddress *microv1.ServiceAddress

	// Status contains the response status information.
	Status *status.Status

	// Payload contains the response payload, if any.
	Payload *anypb.Any
}

Response represents a structured response that follows the protobuf schema.

func NewErrorResponse

func NewErrorResponse(req *Request, err error, code int32) *Response

NewErrorResponse creates an error response with the given error.

func NewSuccessResponse

func NewSuccessResponse(req *Request, payload proto.Message) *Response

NewSuccessResponse creates a successful response with optional payload.

func (*Response) Bytes

func (r *Response) Bytes() ([]byte, error)

Bytes returns the response as a byte slice ready to be sent over NATS.

type Service

type Service struct {

	// Service address information
	Address      *ServiceAddress
	ProtoAddress *microv1.ServiceAddress
	Version      string // Version represents the service version.
	// contains filtered or unexported fields
}

Service represents a micro service that can serve requests.

func NewService

func NewService(client *Client, address *ServiceAddress, tel *telemetry.Telemetry) (*Service, error)

NewService creates a new service with the given NATS client, service address, and telemetry.

func (*Service) AddEndpoint

func (s *Service) AddEndpoint(name string, handler Handler) error

AddEndpoint adds an endpoint to the service. The endpoint will be registered under the service's address as a prefix.

Example:

svc.AddEndpoint("ping", handlePing)   // -> "<service_address>.ping"

func (*Service) AddGroup

func (s *Service) AddGroup(name string) *ServiceEndpointGroup

AddGroup returns a helper struct that allows registering a group of endpoints with a common prefix. For example, all endpoints in the "message" group will be registered as "message.<endpoint_name>".

Example:

messageGroup := svc.AddGroup("message")
messageGroup.AddEndpoint("send", handleMessageSend)   // -> "<service_address>.message.send"
messageGroup.AddEndpoint("delete", handleMessageDelete)   // -> "<service_address>.message.delete"
messageGroup.AddEndpoint("update", handleMessageUpdate)   // -> "<service_address>.message.update"

func (*Service) Close

func (s *Service) Close() error

Close closes all the endpoints registered with the service.

func (*Service) Logger

func (s *Service) Logger() *zerolog.Logger

Logger returns a logger for the service with service-specific context.

func (*Service) NATS

func (s *Service) NATS() *Client

NATS returns the underlying NATS client. While it is possible to use the NATS client directly to publish and subscribe to endpoints, it is recommended to use the Service methods to handle messages received from NATS.

type ServiceAddress

type ServiceAddress = microv1.ServiceAddress

ServiceAddress is an alias to the protobuf-generated ServiceAddress type.

func GetAddress

func GetAddress(region string, realm Realm, organization, project string, serviceID ServiceID) *ServiceAddress

GetAddress creates a new ServiceAddress with the given realm, organization, project, and service ID.

func ParseAddress

func ParseAddress(address string) (*ServiceAddress, error)

ParseAddress parses a string into a ServiceAddress. The format should be "<region>.<realm>.<organization>.<project>.<service_id>".

type ServiceEndpointGroup

type ServiceEndpointGroup struct {
	// contains filtered or unexported fields
}

ServiceEndpointGroup is a helper struct that allows registering a group of endpoints with a common prefix.

func (*ServiceEndpointGroup) AddEndpoint

func (g *ServiceEndpointGroup) AddEndpoint(name string, handler Handler) error

type ServiceID

type ServiceID = string

ServiceID represents a unique identifier for a service.

type Shard

type Shard struct {
	// contains filtered or unexported fields
}

Shard manages the shard lifecycle depending on the operation mode.

func NewShard

func NewShard(base ShardEngine, opts ShardOptions) (*Shard, error)

NewShard creates a new shard instance with the given base implementation and options.

func (*Shard) Base

func (s *Shard) Base() ShardEngine

Base returns the underlying shard engine implementation.

func (*Shard) CurrentTick

func (s *Shard) CurrentTick() (Tick, error)

CurrentTick returns the current tick information. Returns an error if called during the inter-tick period, whic happens after an epoch is published but before the next tick is started.

func (*Shard) IsDisablePersona

func (s *Shard) IsDisablePersona() bool

func (*Shard) Mode

func (s *Shard) Mode() ShardMode

Mode returns the current operating mode of the shard (leader, follower, or undefined).

func (*Shard) Run

func (s *Shard) Run(ctx context.Context) error

Run starts the shard's main execution loop.

type ShardCommand

type ShardCommand interface {
	Name() string
}

type ShardEngine

type ShardEngine interface {
	// Init initializes the shard's initial state.
	Init() error

	// Tick processes the given input and advances the shard state.
	Tick(Tick) error

	// Replay processes the given input during state reconstruction.
	// This should produce the same result as Tick for deterministic replay.
	Replay(Tick) error

	// StateHash returns a hash of the current shard state for consistency verification.
	StateHash() ([]byte, error)

	// Snapshot captures the current shard state and returns it as serialized bytes.
	Snapshot() ([]byte, error)

	// Restore restores the shard state from the given serialized bytes.
	Restore(data []byte) error

	// Reset resets the engine to its clean initial state.
	Reset()
}

ShardEngine defines the interface that must be implemented by specific shard types. It provides the core functionality for initializing, processing inputs, and maintaining state.

type ShardMode

type ShardMode uint8

ShardMode defines the operational mode of a shard instance.

const (
	ModeUndefined ShardMode = iota // Used as the zero value
	ModeLeader                     // Leader mode processes input and publishes epochs
	ModeFollower                   // Follower mode consumes epochs and replays state
)

func (ShardMode) String

func (m ShardMode) String() string

type ShardOptions

type ShardOptions struct {
	Client                 *Client                // NATS client
	Address                *ServiceAddress        // Shard's service address
	Mode                   ShardMode              // Operation mode (Leader or Follower)
	EpochFrequency         uint32                 // Number of ticks per epoch
	TickRate               float64                // Number of ticks per second
	Telemetry              *telemetry.Telemetry   // Telemetry for logging and tracing
	SnapshotStorageType    SnapshotStorageType    // Snapshot storage type
	SnapshotStorageOptions SnapshotStorageOptions // Optional snapshot storage options
	SnapshotFrequency      uint32                 // Number of epochs per snapshot
	DisablePersona         bool                   // Disable persona verification for development/testing
	EpochStreamMaxBytes    uint32                 // Maximum bytes for epoch stream (required by some NATS providers)
}

ShardOptions contains configuration options for creating a new shard.

type Snapshot

type Snapshot = microv1.Snapshot

Snapshot represents a point-in-time capture of shard state. This is an alias to the protobuf-generated type for better API ergonomics.

type SnapshotStorage

type SnapshotStorage interface {
	// Store saves the snapshot, atomically replacing any existing snapshot.
	// The previous snapshot should be preserved as backup if possible.
	Store(snapshot *Snapshot) error

	// Load retrieves the current snapshot.
	// Returns an error if no snapshot exists.
	Load() (*Snapshot, error)

	// Exists checks if a current snapshot is available.
	Exists() bool
}

SnapshotStorage provides persistence for shard snapshots. Implementations handle atomic storage with automatic backup of previous snapshots.

type SnapshotStorageOptions

type SnapshotStorageOptions interface {
	// contains filtered or unexported methods
}

type SnapshotStorageType

type SnapshotStorageType uint8

SnapshotStorageType defines the type of snapshot storage to use.

const (
	SnapshotStorageUndefined SnapshotStorageType = iota
	SnapshotStorageNop
	SnapshotStorageJetStream
)

func (SnapshotStorageType) String

func (s SnapshotStorageType) String() string

type Tick

type Tick struct {
	Header TickHeader // Metadata about when and which tick this represents
	Data   TickData   // The actual commands processed during this tick
}

Tick represents a single execution step in the shard's lifecycle.

type TickData

type TickData struct {
	Commands []Command // List of verified commands executed in this tick
}

TickData contains the commands that were processed during this tick execution.

type TickHeader

type TickHeader struct {
	TickHeight uint64    // Tick height
	Timestamp  time.Time // When this tick was created
}

TickHeader contains metadata about the tick execution.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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