Cardinal
API Reference
Systems

Systems

Systems are the functions that run every tick. Systems must adhere to the following function signature:

type System func(cardinal.WorldContext) error

Systems are given a WorldContext object which can be used to iterate over transactions, modify component values, search for entities, and log data.

The WorldContext object is an interface with the following methods:

type WorldContext interface {
	// NewSearch creates a new Search object that can iterate over entities that match
	// a given Component filter.
	//
	// For example:
	// search, err := worldCtx.NewSearch(cardinal.Exact(Health{}))
	// if err != nil {
	// 		return err
	// }
	// err = search.Each(worldCtx, func(id cardinal.EntityID) bool {
	// 		...process each entity id...
	// }
	// if err != nil {
	// 		return err
	// }
	NewSearch(filter Filter) (*Search, error)
 
	// CurrentTick returns the current game tick of the world.
	CurrentTick() uint64
 
	// EmitEvent broadcasts an event message to all subscribed clients.
	EmitEvent(event string)
 
	// Logger returns a zerolog.Logger. Additional metadata information is often attached to
	// this logger (e.g. the name of the active System).
	Logger() *zerolog.Logger
 
	getECSWorldContext() ecs.WorldContext
}

Getting Transactions

In order to get specific transactions from the queue, utilize a TransactionType's In method. This TransactionType method takes the WorldContext as an argument, and returns a slice of TxData, which contains data related to that transaction.

import "pkg.world.dev/world-engine/cardinal"
 
func AttackSystem(worldCtx cardinal.WorldContext) error {
    txs := AttackTransaction.In(worldCtx)
    for _, tx := range txs {
        // do something with each tx...
		msg := tx.Msg()
		hash := tx.Hash()
		txData := tx.Tx()
	}
}
 

TxData

type TxData[Input any] struct {
    Hash    message.TxHash      // unique transaction hash
    Msg     Msg                 // transaction input
    Tx      *sign.Transaction   // Transaction data (signature, hash, message data)
}

Example System

import "pkg.world.dev/world-engine/cardinal"
 
// define a component
type Health struct {
	Amount uint64
	Cap    uint64
}
 
func (Health) Name() string {
	return "health"
}
 
// define an attack transaction
type AttackInput struct {
	TargetPlayer uint64
	Amount       uint64
}
 
type AttackResult struct {
	Success bool
}
 
var Attack = cardinal.NewMessageType[AttackInput, AttackResult]("attack")
 
// implement the system
func AttackSystem(worldCtx cardinal.WorldContext) error {
	// get all attack transactions in the queue
	txs := Attack.In(worldCtx)
 
	for _, tx := range txs {
		// get the underlying attack tx struct
		msg := tx.Msg()
 
		// update the component based on the attack
		err := HealthComponent.Update(worldCtx, entity.ID(msg.TargetPlayer), func(health Health) Health {
			health.Amount -= msg.Amount
			return health
		})
		// if an error occurred, set the result, and add the error to the transaction.
		if err != nil {
			Attack.SetResult(world, tx.Hash(), AttackResult{Success: false})
			Attack.AddError(world, tx.Hash(), err)
			continue
		}
		// set result for success case
		Attack.SetResult(world, tx.Hash(), AttackResult{Success: true})
	}
	return nil
}