NewWorld
NewWorld
creates a new World
object.
func NewWorld(opts ...WorldOption) (*World, error)
Parameters
Parameter | Type | Description |
---|
opts | …WorldOption | Options for configuring the world. |
Return Values
Type | Description |
---|
*World | A pointer to the newly created World instance. |
error | An error indicating any issues during creation. |
Options
WithCustomMockRedis
The WithCustomMockRedis
option uses the given miniredis instance as the storage layer for the game state. Game state saved to these instances of miniredis are not persistent across world restarts, so this should only be used for local development and testing.
func WithCustomMockRedis(miniRedis *miniredis.Miniredis) WorldOption
Parameters
Parameter | Type | Description |
---|
miniRedis | *miniredis.Miniredis | A miniredis instance. |
WithDisableSignatureVerification
The WithDisableSignatureVerification
option disables signature verification on the World’s server. This should only be used for testing.
func WithDisableSignatureVerification() WorldOption
Parameters
This method has no parameters.
WithPort
The WithPort
option allows for a custom port to be set for the World’s server. If this option is unset it uses a default port of “4040”.
func WithPort(port string) WorldOption
Parameters
Parameter | Type | Description |
---|
port | string | The port number for the world’s server. |
WithPrettyLog
The WithPrettyLog
option enables JSON parsing and colorized, human-friendly formatted logs using zerolog.
func WithPrettyLog() WorldOption
Parameters
This method has no parameters.
WithReceiptHistorySize
The WithReceiptHistorySize option specifies the number of ticks for which the World object retains receipts. For instance, at tick 40 with a receipt history size of 5, the World stores receipts from ticks 35 to 39. Upon reaching tick 41, it will hold receipts for ticks 36 to 40. If this option remains unset, it defaults to a history size of 10. Game clients can get receipts via the /query/receipts/list endpoint. Nakama also uses this endpoint to transmit receipts to listening clients.
func WithReceiptHistorySize(size int) WorldOption
Parameters
Parameter | Type | Description |
---|
size | int | The size of the receipt history to be set for World. |
Setting a very large receipt history size may impact memory usage and performance. Choose a size that balances your game’s needs with resource constraints.
WithStoreManager
The WithStoreManager
option overrides the default gamestate manager. The gamestate manager is responsible for storing entity and component information, and recovering those values after a world restart. A default manager will be created if this option is unset.
func WithStoreManager(s gamestate.Manager) WorldOption
Parameters
Parameter | Type | Description |
---|
s | gamestate.Manager | The replacement game-state manager. |
WithTickChannel
The WithTickChannel
option sets a channel that will be used to start each tick. A game tick will be started each time a message appears on the given channel. A custom tick rate can be set using time.Tick. This is also useful in tests to manually start ticks. If unset, a default tick rate of 1 per second is used.
func WithTickChannel(ch <-chan time.Time) WorldOption
Parameters
Parameter | Type | Description |
---|
ch | <-chan time.Time | The channel that will start each tick. |
Example
// Example 1: Set tick rate to 500ms (2 ticks per second)
opt := WithTickChannel(time.Tick(500*time.Millisecond))
// Example 2: Set tick rate to 50ms (20 ticks per second) for fast-paced games
opt := WithTickChannel(time.Tick(50*time.Millisecond))
// Example 3: Use a custom channel for manual tick control in tests
tickCh := make(chan time.Time)
opt := WithTickChannel(tickCh)
Choose your tick rate carefully based on your game’s requirements. Higher tick rates provide smoother updates but require more processing power and network bandwidth.
WithTickDoneChannel
The WithTickDoneChannel
option sets a channel that will receive the just-completed tick number each time a tick completes execution. All systems are guaranteed to have been called when a message appears on this channel. This is particularly useful in tests to ensure your systems have fully executed before checking expectations.
func WithTickDoneChannel(ch chan<- uint64) WorldOption
Parameters
Parameter | Type | Description |
---|
ch | chan<- uint64 | The channel that will be notified at the end of each tick. |
Example
// Example usage in tests
func TestGameSystem(t *testing.T) {
tickCh := make(chan time.Time)
doneCh := make(chan uint64)
world, _ := cardinal.NewWorld(
WithTickChannel(tickCh),
WithTickDoneChannel(doneCh),
)
// Start game in a goroutine
go world.StartGame()
// Trigger a tick
tickCh <- time.Now()
// Wait for tick to complete
<-doneCh
// Now safe to check game state
// Your test assertions here...
}
The WithTickDoneChannel is essential for writing deterministic tests. Always wait for the done signal before making assertions about game state changes.
WithMessageExpiration
The WithMessageExpiration
option controls how long messages will live past their creation time on the sender before they are considered to be expired and will not be processed. Default is 10 seconds. For longer expiration times you may also need to set a larger hash cache size using the WithHashCacheSize
option. This setting is ignored if the DisableSignatureVerification option is used. NOTE: this means that the real time clock for the sender and receiver must be synchronized
func WithMessageExpiration(seconds uint) WorldOption
Parameters
Parameter | Type | Description |
---|
seconds | uint | How long messages live past their creation time on the sender. |
WithHashCacheSize
The WithHashCacheSize
option sets how big (in kilobytes) the cache of hashes used for replay protection is allowed to be. Values less than 512 will be treated as 512 (512K cache size). Default is 1024 (1MB cache size). This setting is ignored if the DisableSignatureVerification option is used
func WithHashCacheSize(sizeKB uint) WorldOption
Parameters
Parameter | Type | Description |
---|
sizeKB | uint | How big the cache for used hashes can be. Min value 512. |
RegisterSystems
RegisterSystems
registers one or more systems to the World
. Systems are executed in the order of which they were added to the world.
func RegisterSystems(w *World, s ...cardinal.System)
Example
package main
import (
"github.com/my-username/my-world-engine-project/systems"
"pkg.world.dev/world-engine/cardinal"
)
func main() {
// ... world setup ...
// Systems will run in order in which they are added:
// 1. MoveSystem
// 2. HealthRegenSystem
// 3. AttackSystem
cardinal.RegisterSystems(world,
systems.MoveSystem,
systems.HealthRegenSystem,
systems.AttackSystem)
}
RegisterInitSystems
RegisterInitSystems
registers one or more init systems to the World
. Init systems are executed exactly one time on tick 0. Init systems will not be run when loading a pre-existing world from permanent storage (e.g. on a server restart).
func RegisterInitSystems(world *World, s ...cardinal.System)
Example
package main
import (
"github.com/my-username/my-world-engine-project/systems"
"pkg.world.dev/world-engine/cardinal"
)
func main() {
// ... world setup ...
// Systems will be run one time on tick 0
// 1. CreateEntities
// 2. InitializeHealth
cardinal.RegisterSystems(world,
systems.CreateEntities,
systems.InitializeHealth,
)
}
Parameters
Parameter | Type | Description |
---|
world | *World | A pointer to a World instance. |
s | …System | Variadic parameter for init systems to be added to the World. |
RegisterComponents
RegisterComponents
registers one or more components to the World
. Upon registration, components are assigned an ID. IDs are assigned incrementally, starting from 0, in the order in which they were passed to the method.
RegisterComponents
can be only be called once. Subsequent calls to the method will return an error.
func RegisterComponent[T metadata.Component](world *World) error
Example
package main
import (
"log"
"github.com/my-username/my-world-engine-project/component"
"pkg.world.dev/world-engine/cardinal"
)
func main() {
// ... world setup ...
err := cardinal.RegisterComponent[LocationComponet](world)
if err != nil {
log.Fatal(err)
}
err = cardinal.RegisterComponent[AttackPowerComponent](world)
if err != nil {
log.Fatal(err)
}
err = cardinal.RegisterComponent[HealthComponent](world)
if err != nil {
log.Fatal(err)
}
// Alternative RegisterComponent pattern with less error checking:
err = errors.Join(
cardinal.RegisterComponent[LocationComponet](world),
cardinal.RegisterComponent[AttackPowerComponent](world),
cardinal.RegisterComponent[HealthComponent](world),
)
if err != nil {
log.Fatal(err)
}
}
Parameters
Parameter | Type | Description |
---|
T | type parameter | A component struct that implements the Name method. |
world | *World | A pointer to a World instance. |
Return Value
Type | Description |
---|
error | An error indicating any issues that occurred during the component registration. |
RegisterQuery
RegisterQuery
registers the queries in the World
. This allows the Query
endpoints to be automatically generated.
func RegisterQuery[Request, Reply any](
world *World,
name string,
handler func(engine.Context, req *Request) (*Reply, error),
opts ...QueryOption[Request, Reply]) error
Example
package main
import (
"log"
"github.com/my-username/my-world-engine-project/query"
"pkg.world.dev/world-engine/cardinal"
)
func main() {
// ... world setup ...
err := cardinal.RegisterQuery[query.HealthRequest, query.HealthResponse](
world,
"query_health",
func(worldCtx cardinal.WorldContext, req *query.HealthRequest) (reply *query.HealthReply, err error) {
// ...fetch relevant health from worldCtx...
return &HealthReply{}, nil
})
if err != nil {
log.Fatal(err)
}
}
Parameters
Parameter | Type | Description |
---|
Request | type parameter | The input type of this query |
Reply | type parameter | The output type of this query |
world | *World | A pointer to a World instance. |
name | string | The name of the server endpoint to use this query |
handler | func(engine.Context, *Request) (Reply, error) | The handler to execute the logic of this query |
opts | …QueryOption[Request, Reply] | Variadic options to augment the behavior of this query |
Return Value
Type | Description |
---|
error | An error indicating any issues that occurred during the query registration. |
RegisterMessages
RegisterMessages
registers messages in the World
. This allows message endpoints to be automatically generated.
RegisterMessages
can be only be called once. Subsequent calls to the method will return an error.
func RegisterMessages(world *World, msgs ...AnyMessage) error
Example
package main
import (
"log"
"github.com/my-username/my-world-engine-project/msg"
"pkg.world.dev/world-engine/cardinal"
)
func main() {
// ... world setup ...
err := cardinal.RegisterMessages(world,
msg.Move,
msg.Attack,
)
if err != nil {
log.Fatal(err)
}
}
Parameters
Parameter | Type | Description |
---|
world | *World | A pointer to a World instance. |
msgs | …AnyMessage | Variadic parameter for Message instances to be registered. |
Return Value
Type | Description |
---|
error | An error indicating any issues that occurred during the message registration. |
StartGame
StartGame
starts the game by loading any previously saved game state, spinning up the message/query handler, and starting the game ticks. This method blocks the main Go routine. If for whatever reason execution needs to continue after calling this method, it should be called in a separate go routine.
func (w *World) StartGame() error
Return Value
Type | Description |
---|
error | An error indicating any issues when starting the game. |