peex

package module
v0.0.0-...-88f48e0 Latest Latest
Warning

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

Go to latest
Published: Jul 12, 2023 License: MIT Imports: 18 Imported by: 0

README

Peex

A multi-handler & player session system for Dragonfly, partly inspired by ECS and Dragonfly's command system.

Peex aims to keep a modular approach, without boilerplate code while keeping enough speed and simplicity. I have personally tried multiple approaches for multiple handlers per player in the past, from manually calling other handlers in the main handler to more sophisticated approaches. Ultimately I think this approach is my favourite one so far.

How it works

Basics

This section will show the basics of how peex works. The example used here will be a basic implementation of some sort of minigame system.

The manager & sessions

Firstly you will need to make a new *peex.Manager. This will store all active sessions, and will allow you to assign a session to a player.

manager := peex.New(peex.Config{
	// ... (some fields are omitted)
	Handlers: []peex.Handler{ /* ... handlers go here (more on that shortly). */ }
})

// Ideally, run this when the player joins to assign them a session.
session := manager.Accept(player)

As can be seen in this example, you can provide all handlers that will run when creating the manager. They cannot be added after it has been created. You can still control when handlers run using components. Let's go over those first before explaining handlers in more detail.

Components

Components are what actually stores a player's data. A player can have multiple components, but they are stored by type so multiple components of the same type is not possible. They are usually simple structs with data, or pointers to ones. Keep in mind that if your component is not a pointer it cannot be modified in handlers.

In our example, lets create a MinigamePlayer component.

type MinigamePlayer struct {
    Game  *Minigame
    Score int
    Team  Team
}

That's all you need to do! You can add any number of fields (or no fields), just like a normal struct. To give a player this component, you can do the following:

err := session.InsertComponent(&MinigamePlayer{
    // values...
})

The function will return an error if a player already has a component of said type. Use session.SetComponent(component) to set or overwrite a component regardless of whether it was already present. Components can also be removed using session.RemoveComponent(component). This will remove the component with the same type as the argument, if it exists, and return it.

In our example you would add the component when a player joins a minigame and remove it when they leave it.

Handlers

Now that our player has components, we can write handlers to handle events for the player. A handler is just a struct that implements some methods fom player.Handler. Note that your handler does not actually need to implement player.Handler. In fact, it is recommended to not implement events you dont use for performance reasons.

Struct fields can be used to add different queries to the handler. The handler will only run if all the queried components are present in the session and will also allow the handler to access these values.

Let's create a handler that will handle events when the player is in a minigame. We will make a simple one that subtracts from the score when the player dies.

type MinigameHandler struct {
    // peex will set the first *player.Player field it finds to the 
    // player that is the events. Has to be exported!
    Player  *player.Player
    Session *peex.Session // same as above but for *session.Session
    Manager *peex.Manager // ^
    
    // This parameter will make it so the handler only runs when the
    // specified component type is present. Different query types
    // also exist, like With if you do not wish to access any values
    // and Optional, which will make the handler run even if the
    // component is not present. All queries need to be exported!
    MinigamePlayer peex.Query[*MinigamePlayer]
    // You can add as many queries for different types as you like!
}

func (m MinigameHandler) HandleDeath() {
    m.MinigamePlayer.Load().Score -= 1
}

As seen before, handlers need to be registered when creating the manager. This means you cannot remove handlers on runtime. This should not be a problem due to the query system: you can specify which handlers run by adding or removing components to/from a session. When you register a handler to the manager, it will automatically detect which events are implemented and only handle those events.

Query functions

Sometimes you want to run some logic on certain components, or only if certain components are present. You can either use component, ok := session.Component(type), or use the session.Query(queryFuncion) method.

A query function is similar to a handler: you can specify queries as function parameters, and the query will only run if all component are present. Lets run a query to change a player's team, which would for example be useful in a /changeteam command.

didRun := session.Query(func(q1 peex.Query[*MinigamePlayer]) {
    q1.Load().Team = newTeam
})

Here didRun is a boolean that returns whether the query was able to run or not. In query functions the peex.Query[] around the component type can be omitted. When using another query type like Option, you will still need to include it.

You can also run queries on multiple players at once, using the manager.QueryAll() method. This works the same as session.Query(), just for every player. The method will return the amount of players the function actually ran for.

Data Persistence

You may want to automatically load and save data for some components. This is also supported in the library using component providers, and working with persistent data for both online and offline players is similar to before.

Providers

A provider is just a struct that implements a Save and Load method for a component. Say we want to have a provider for *SampleComponent, the provider would look something like this:

type SampleProvider struct { /* ... */ }

func (SampleProvider) Load(id uuid.UUID, comp *SampleComponent) error {
	/* implementation ... */
}

func (SampleProvider) Save(id uuid.UUID, comp *SampleComponent) error {
    /* implementation ... */
}

Note that the component type must be a pointer.

After creating the provider for the component, you may register it to the manager by adding it to the peex.Config. It needs to be wrapped in a peex.ProviderWrapper to allow peex to use the provider regardless of the component type while keeping strict typing.

manager := peex.New(peex.Config{
	// ... (some fields are omitted)
	Providers: []peex.ComponentProvider{
		peex.WrapProvider(SampleProvider{}),
		/* ... more providers go here. */
	}
})

Now, when the SampleComponent is inserted into a session, the provider will first have its Load function called to load any data into the component. When the component is removed, it will also be saved again.

Notice that we did not have to modify the actual component at all. This allows for providers to be seamlessly swapped out.

UUID Queries

You may want to query a component regardless of whether the player's session currently has this component, or even when the player is offline altogether. For this you can perform a query function by a player UUID.

This is almost the same as a normal query, except it will try to load a component if it was not present in the session or the player is offline. The query will not run if at least one component is not present in the session and it has no provider, or if a component could not be loaded. Any loaded components will be saved again after the function has run. An error is returned when there was an error loading or saving a component.

didRun, err := manager.QueryID(func(q1 peex.Query[*SampleComponent]) {
    /* do stuff */
})

Documentation

Overview

Package peex This file was generated using the event generator. Do not edit.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ComponentFromSession

func ComponentFromSession[T Component](s *Session) (T, bool)

ComponentFromSession returns and automatically type casts a user's component to the correct type if it is present.

Types

type Adder

type Adder interface {
	Component
	// Add gets called right after the component is added to a Session. This is always called regardless of how the
	// component is added. It is also called when another component of the same type was present but got replaced. The
	// owner of the session will be passed along as an argument. Gets called after the component has been loaded.
	Add(p *player.Player)
}

Adder represents a Component that has a function that is called when the component is added to a Session in any way.

type Component

type Component interface{}

Component represents some data type that can be stored in a player session. There can only be one component of each type per player, but each player does not necessarily need to have every component.

type ComponentProvider

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

ComponentProvider is the interface representation of any type of ProviderWrapper, allowing them to be passed in the Config.

type Config

type Config struct {
	// Logger allows for an optional logger to be supplied. This will log things such as errors when saving components
	// when a player is leaving.
	Logger server.Logger
	// Handlers contains all the handlers that will run during the lifetime of the manager. These will always be active,
	// but can be controlled through adding or removing components from users.
	Handlers []Handler
	// Providers allows for passing of a list of ComponentProviders which can load & save components for players at
	// runtime. The providers must be wrapped in a ProviderWrapper using the WrapProvider function.
	Providers []ComponentProvider
}

Config is a struct passed to the New function when creating a new Manager. It allows for customizing several aspects such as specifying handlers and component providers. Many of these cannot be modified after the manager has been created.

type GenericProvider

type GenericProvider[c Component] interface {
	// Load loads & writes data stored under the provider UUID to the pointer to the component c. Comp can be nil if it
	// is being loaded from an offline player.
	Load(id uuid.UUID, comp *c) error
	// Save writes the component to storage, using the UUID to identify the owner of the data.
	Save(id uuid.UUID, comp *c) error
}

GenericProvider represent a struct that can load & save data associated to a player for a certain component.

type Handler

type Handler interface {
}

Handler is a struct that handles player-related events. It can query for certain components contained in the player session, and will only run if all those components are present.

type Manager

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

Manager stores all current sessions. It also contains all the registered handlers and component types.

func New

func New(cfg Config) *Manager

New creates a new Session Manager. It also inserts all the provided handlers into the manager. Events will be called in the order the handlers are added to the manager. These handlers can query for specific components to be present in a player Session in order to actually run.

func (*Manager) Accept

func (m *Manager) Accept(p *player.Player, components ...Component) (*Session, error)

Accept assigns a Session to a player. This also works for disconnected players or fake players. Initial components can be provided for the player to start with. The add function will be called on any component that implements Adder. Providing multiple components of the same type is not allowed and will return an error.

func (*Manager) QueryAll

func (m *Manager) QueryAll(queryFunc any) int

QueryAll runs a query on all currently online players. This works the same as if a query is run on every Session separately (albeit slightly faster). A number of players on which the query executed successfully is returned.

func (*Manager) QueryID

func (m *Manager) QueryID(id uuid.UUID, queryFunc any) (bool, error)

QueryID executes a query on a player by their UUID, regardless of whether they are online or not. If the player is online with a stored session, components will be fetched from that session. If the player is not online, or one of the components is not present, those components will be loaded if they have a provider. In this case, if at least one component has no provider or there was a provider error, the query will not run. Loaded components will be saved again. Returns any error that occurred and whether the query ran. Should be handled independently.

func (*Manager) SessionFromPlayer

func (m *Manager) SessionFromPlayer(p *player.Player) (*Session, bool)

SessionFromPlayer returns a player's session.

func (*Manager) SessionFromUUID

func (m *Manager) SessionFromUUID(id uuid.UUID) (*Session, bool)

SessionFromUUID returns the session of the player with the corresponding UUID. Only works for currently online players.

func (*Manager) Sessions

func (m *Manager) Sessions() []*Session

Sessions returns every session currently stored in the manager.

type Option

type Option[c Component] struct {
	With[c]
	// contains filtered or unexported fields
}

Option is a query that allows the query to specify whether a certain Component is present in the session, passing along its value if it exists. This value can be accessed through Option.Load().

func (Option[c]) Load

func (o Option[c]) Load() (c, bool)

Load returns the queried value, along with whether it actually exists.

type ProviderWrapper

type ProviderWrapper[c Component] struct {
	// contains filtered or unexported fields
}

ProviderWrapper is a wrapper around a GenericProvider to ensure strict typing of components and to easily allow for Peex to resolve the component type.

func WrapProvider

func WrapProvider[c Component](p GenericProvider[c]) ProviderWrapper[c]

WrapProvider creates a new wrapper around a provider of the desired type.

type Query

type Query[c Component] struct {
	With[c]
	// contains filtered or unexported fields
}

Query is used to query for a certain component type, passing its value to the handler. Query.Load() can be used to get the value.

func (Query[c]) Load

func (q Query[c]) Load() c

Load returns the underlying value of the Query.

type Remover

type Remover interface {
	Component
	// Remove gets called right before the current component instance gets removed from the Session. This means the
	// method is also called when the component gets replaced with another of the same type. The owner of the session is
	// passed along as argument. Gets called before the component is saved.
	Remove(p *player.Player)
}

Remover represents a Component that has extra logic that runs when the component is removed from a session in any way.

type Session

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

Session is a unique object that stores a player's data and handles player events. Data is stored in components, which can be added and removed from the Session at any time.

func (*Session) Component

func (s *Session) Component(c Component) (Component, bool)

Component returns the Component in the Session of the same type as the argument if it was found.

func (*Session) HandleAttackEntity

func (s *Session) HandleAttackEntity(ctx *event.Context, e world.Entity, force, height *float64, critical *bool)

func (*Session) HandleBlockBreak

func (s *Session) HandleBlockBreak(ctx *event.Context, pos cube.Pos, drops *[]item.Stack, xp *int)

func (*Session) HandleBlockPick

func (s *Session) HandleBlockPick(ctx *event.Context, pos cube.Pos, b world.Block)

func (*Session) HandleBlockPlace

func (s *Session) HandleBlockPlace(ctx *event.Context, pos cube.Pos, b world.Block)

func (*Session) HandleChangeWorld

func (s *Session) HandleChangeWorld(before, after *world.World)

func (*Session) HandleChat

func (s *Session) HandleChat(ctx *event.Context, message *string)

func (*Session) HandleCommandExecution

func (s *Session) HandleCommandExecution(ctx *event.Context, command cmd.Command, args []string)

func (*Session) HandleDeath

func (s *Session) HandleDeath(src world.DamageSource, keepInv *bool)

func (*Session) HandleExperienceGain

func (s *Session) HandleExperienceGain(ctx *event.Context, amount *int)

func (*Session) HandleFoodLoss

func (s *Session) HandleFoodLoss(ctx *event.Context, from int, to *int)

func (*Session) HandleHeal

func (s *Session) HandleHeal(ctx *event.Context, health *float64, src world.HealingSource)

func (*Session) HandleHurt

func (s *Session) HandleHurt(ctx *event.Context, damage *float64, attackImmunity *time.Duration, src world.DamageSource)

func (*Session) HandleItemConsume

func (s *Session) HandleItemConsume(ctx *event.Context, item item.Stack)

func (*Session) HandleItemDamage

func (s *Session) HandleItemDamage(ctx *event.Context, i item.Stack, damage int)

func (*Session) HandleItemDrop

func (s *Session) HandleItemDrop(ctx *event.Context, e world.Entity)

func (*Session) HandleItemPickup

func (s *Session) HandleItemPickup(ctx *event.Context, i *item.Stack)

func (*Session) HandleItemUse

func (s *Session) HandleItemUse(ctx *event.Context)

func (*Session) HandleItemUseOnBlock

func (s *Session) HandleItemUseOnBlock(ctx *event.Context, pos cube.Pos, face cube.Face, clickPos mgl64.Vec3)

func (*Session) HandleItemUseOnEntity

func (s *Session) HandleItemUseOnEntity(ctx *event.Context, e world.Entity)

func (*Session) HandleJump

func (s *Session) HandleJump()

func (*Session) HandleLecternPageTurn

func (s *Session) HandleLecternPageTurn(ctx *event.Context, pos cube.Pos, oldPage int, newPage *int)

func (*Session) HandleMove

func (s *Session) HandleMove(ctx *event.Context, newPos mgl64.Vec3, newYaw, newPitch float64)

func (*Session) HandlePunchAir

func (s *Session) HandlePunchAir(ctx *event.Context)

func (*Session) HandleQuit

func (s *Session) HandleQuit()

func (*Session) HandleRespawn

func (s *Session) HandleRespawn(pos *mgl64.Vec3, w **world.World)

func (*Session) HandleSignEdit

func (s *Session) HandleSignEdit(ctx *event.Context, frontSide bool, oldText, newText string)

func (*Session) HandleSkinChange

func (s *Session) HandleSkinChange(ctx *event.Context, skin *skin.Skin)

func (*Session) HandleStartBreak

func (s *Session) HandleStartBreak(ctx *event.Context, pos cube.Pos)

func (*Session) HandleTeleport

func (s *Session) HandleTeleport(ctx *event.Context, pos mgl64.Vec3)

func (*Session) HandleToggleSneak

func (s *Session) HandleToggleSneak(ctx *event.Context, after bool)

func (*Session) HandleToggleSprint

func (s *Session) HandleToggleSprint(ctx *event.Context, after bool)

func (*Session) HandleTransfer

func (s *Session) HandleTransfer(ctx *event.Context, addr *net.UDPAddr)

func (*Session) InsertComponent

func (s *Session) InsertComponent(c Component) error

InsertComponent adds the Component to the player, keeping all it's values. An error is returned if the Component was already present. Also loads the component if a provider for it has been set in the config.

func (*Session) Player

func (s *Session) Player() *player.Player

Player returns the Player that owns the Session. Returns nil if the Session is owned by a player that is no longer online.

func (*Session) Query

func (s *Session) Query(queryFunc any) bool

Query runs a query function on the session. This function must use the Query, With and Option types as input parameters. These will work like they do in a Handler. True is returned if the query actually ran, else false.

func (*Session) RemoveComponent

func (s *Session) RemoveComponent(c Component) (Component, error)

RemoveComponent tries to remove the component with the same type as the provided argument from the Session. The value of the component will also be returned, If the Session does not have the component, nothing happens, and nil is returned. Also saves the component if a provider for it has been set in the config.

func (*Session) Save

func (s *Session) Save(c Component) error

Save saves a single component type for the session. Saves the component of the same type as the argument that is currently present as opposed to the one provided as argument.

func (*Session) SaveAll

func (s *Session) SaveAll() error

SaveAll saves every component that can be saved. If for any component an error is returned, the last error will be returned by this function.

func (*Session) SetComponent

func (s *Session) SetComponent(c Component)

SetComponent updates the value of a Component currently present in the Session. This is done regardless of whether this Component was present before. If the component was already present, and it implements Remover, the Remove method will first be called on the previous instance of the component. The Add method will be called on the new Component if it implements Adder.

NOTE: does NOT load the component!

type With

type With[c Component] struct{}

With is used in handler queries to denote that the presence of a certain component type is required, but the value of the component itself is not important.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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