forked from Argus-Labs/world-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplugin_task.go
155 lines (129 loc) · 5.37 KB
/
plugin_task.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package cardinal
import (
"fmt"
"github.com/rotisserie/eris"
"pkg.world.dev/world-engine/cardinal/filter"
"pkg.world.dev/world-engine/cardinal/types"
)
// -----------------------------------------------------------------------------
// Public API accessible via cardinal.<function_name>
// -----------------------------------------------------------------------------
// RegisterTask registers a Task definition with the World. A Task definition is a special type of component that
// has a Handle(WorldContext) method that is called when the task is triggered. The Handle method is responsible
// for executing the task and returning an error if any occurred.
func RegisterTask[T Task](w *World) error {
if err := RegisterComponent[T](w); err != nil {
return eris.Wrap(err, "failed to register timestamp task component")
}
var t T
systemName := fmt.Sprintf("task_system_%s", t.Name())
if err := w.SystemManager.registerSystem(false, systemName, taskSystem[T]); err != nil {
return eris.Wrap(err, "failed to register timestamp task system")
}
return nil
}
// -----------------------------------------------------------------------------
// Components
// -----------------------------------------------------------------------------
// Task is a user-facing special component interface that is used to define a task that can be scheduled to be executed.
// It implements the types.Component interface along with a Handle method that is called when the task is triggered.
// This method is not to be confused with taskMetadata, which is an internal component type used to store the trigger
// condition for a task.
type Task interface {
types.Component
Handle(WorldContext) error
}
// taskMetadata is an internal component that is used to store the trigger condition for a task.
// It implements the types.Component interface along with an isTriggered method that returns true if the task
// should be triggered based on the current tick or timestamp.
type taskMetadata struct {
TriggerAtTick *uint64
TriggerAtTimestamp *uint64
}
func (taskMetadata) Name() string {
return "taskMetadata"
}
// Task will be triggered when the current tick is greater than designated trigger tick OR when the current timestamp
// is greater than designated trigger timestamp. A task can only have one trigger condition, either tick or timestamp.
// The task should have been trigger at exactly the designated trigger tick, but we make it >= to be safe.
func (t taskMetadata) isTriggered(tick uint64, timestamp uint64) bool {
if t.TriggerAtTick != nil {
return tick >= *t.TriggerAtTick
}
return timestamp >= *t.TriggerAtTimestamp
}
// -----------------------------------------------------------------------------
// Systems
// -----------------------------------------------------------------------------
// taskSystem is a system that is registered when RegisterTask is called. It is responsible for iterating through all
// entities with the Task type T and executing the task if the trigger condition is met.
func taskSystem[T Task](wCtx WorldContext) error {
var internalErr error
err := NewSearch().Entity(filter.Contains(filter.Component[T](), filter.Component[taskMetadata]())).Each(wCtx,
func(id types.EntityID) bool {
taskMetadata, err := GetComponent[taskMetadata](wCtx, id)
if err != nil {
internalErr = err
return false
}
if taskMetadata.isTriggered(wCtx.CurrentTick(), wCtx.Timestamp()) {
task, err := GetComponent[T](wCtx, id)
if err != nil {
internalErr = err
return false
}
if err = (*task).Handle(wCtx); err != nil {
internalErr = err
return false
}
if err = Remove(wCtx, id); err != nil {
internalErr = err
return false
}
}
return true
},
)
if internalErr != nil {
return eris.Wrap(internalErr, "encountered an error while executing a task")
}
if err != nil {
return eris.Wrap(err, "encountered an error while iterating over tasks")
}
return nil
}
// -----------------------------------------------------------------------------
// Internal functions used by WorldContext to schedule tasks
// -----------------------------------------------------------------------------
// createTickTask creates a task entity that will be executed by taskSystem at the designated tick.
func createTickTask(wCtx WorldContext, tick uint64, task Task) error {
_, err := Create(wCtx, task, taskMetadata{TriggerAtTick: &tick})
if err != nil {
return eris.Wrap(err, "failed to create tick task entity")
}
return nil
}
// createTimestampTask creates a task entity that will be executed by taskSystem at the designated timestamp.
func createTimestampTask(wCtx WorldContext, timestamp uint64, task Task) error {
_, err := Create(wCtx, task, taskMetadata{TriggerAtTimestamp: ×tamp})
if err != nil {
return eris.Wrap(err, "failed to create timestamp task entity")
}
return nil
}
// -----------------------------------------------------------------------------
// Plugin Definition
// -----------------------------------------------------------------------------
var _ Plugin = (*TaskPlugin)(nil)
// TaskPlugin defines a plugin that handles task scheduling and execution.
type TaskPlugin struct{}
func newFutureTaskPlugin() *TaskPlugin {
return &TaskPlugin{}
}
func (*TaskPlugin) Register(w *World) error {
err := RegisterComponent[taskMetadata](w)
if err != nil {
return eris.Wrap(err, "failed to register task entry component")
}
return nil
}