camfish/actorset.go
2024-12-31 01:27:53 -05:00

179 lines
4.7 KiB
Go

package camfish
import "iter"
import "slices"
import "context"
// actorSets stores a sorted set of all actors, as well as information about
// their state.
type actorSets struct {
all actorSet[Actor]
inf map[Actor] actorInfo
nextOrder int
flagAdder actorSet[FlagAdder]
configProcessor actorSet[ConfigProcessor]
configurable actorSet[Configurable]
initializable actorSet[Initializable]
runnable actorSet[Runnable]
trimmable actorSet[Trimmable]
}
// actorInfo holds information about a running actor.
type actorInfo struct {
// ctx is the context of the actor. It is passed to the actor's Run
// method.
ctx context.Context
// done is used to stop the actor. It is created when the actor is added
// to the actorSets.
done func()
// stopped is closed once the actor's run method exits. If the actor
// does not have a run method, it will be nil.
stopped chan struct { }
// order is the semantic order of the actor within the sets. This is
// incremented automatically for each actor that is added. It is never
// less than one.
order int
}
// actorSetIface holds only the add/del/clear methods of actorSet.
type actorSetIface interface {
add(actor Actor)
del(actor Actor)
clear()
}
// All returns an iterator over all sets in the collection.
func (sets *actorSets) All() iter.Seq[actorSetIface] {
return func(yield func(actorSetIface) bool) {
yield(&sets.all)
yield(&sets.flagAdder)
yield(&sets.configProcessor)
yield(&sets.configurable)
yield(&sets.initializable)
yield(&sets.runnable)
yield(&sets.trimmable)
}
}
// add adds an actor under the given parent context. This is a write operation.
func (this *actorSets) add(ctx context.Context, actor Actor) {
if this.inf == nil { this.inf = make(map[Actor] actorInfo)}
actorCtx, done := context.WithCancel(ctx)
this.nextOrder ++
info := actorInfo {
ctx: actorCtx,
done: done,
order: this.nextOrder,
}
if _, ok := actor.(Runnable); ok {
info.stopped = make(chan struct { })
}
this.inf[actor] = info
for set := range this.All() {
set.add(actor)
}
}
// del removes an actor. This is a write operation.
func (this *actorSets) del(actor Actor) {
delete(this.inf, actor)
for set := range this.All() {
set.del(actor)
}
}
// clear removes all actors.
func (this *actorSets) clear() {
clear(this.inf)
for set := range this.All() {
set.clear()
}
}
// info gets information about an actor.
func (this *actorSets) info(actor Actor) actorInfo {
if this.inf == nil { return actorInfo { } }
return this.inf[actor]
}
// sortActors sorts actors according to the order in which they were added.
func sortActors[T comparable] (sets *actorSets, actors []T) []T {
slices.SortFunc(actors, func (left, right T) int {
// :3
leftOrder := sets.info(any(left).(Actor)).order
rightOrder := sets.info(any(right).(Actor)).order
if leftOrder < 1 || rightOrder < 1 {
panic("could not sort actors, some were invalid")
}
switch {
case leftOrder < rightOrder: return -1
case rightOrder > leftOrder: return 1
default: return 0
}
})
return actors
}
// actorSet is a list that holds all actors of a type. it must not return any
// references to data that is referenced elsewehre, except for references to
// actors.
type actorSet[T comparable] struct {
actors map[T] string
}
// add adds an actor, if it is of the set's type
func (this *actorSet[T]) add(actor Actor) {
if this.actors == nil { this.actors = make(map[T] string) }
if item, ok := actor.(T); ok {
this.actors[item] = actor.Type()
}
}
// add removes an actor, if it is of the set's type
func (this *actorSet[T]) del(actor Actor) {
if this.actors == nil { return }
if item, ok := actor.(T); ok {
delete(this.actors, item)
}
}
// clear removes all actors.
func (this *actorSet[T]) clear() {
if this.actors == nil { return }
clear(this.actors)
}
// find returns the first actor with the given type.
func (this *actorSet[T]) find(typ string) T {
for actor, actorType := range this.actors {
if actorType == typ { return actor }
}
var zero T
return zero
}
// findAll returns all actors with the given type. This method allocates and
// returns a slice instead of an iterator for concurrency reasons.
func (this *actorSet[T]) findAll(typ string) []T {
if this.actors == nil { return nil }
slice := []T { }
for actor, actorType := range this.actors {
if actorType == typ {
slice = append(slice, actor)
}
}
return slice
}
// all returns all actors. This method allocates and returns a slice instead of
// an iterator for concurrency reasons.
func (this *actorSet[T]) all() []T {
if this.actors == nil { return nil }
slice := []T { }
for actor := range this.actors {
slice = append(slice, actor)
}
return slice
}