forked from rgriege/Violet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
event.h
234 lines (203 loc) · 8.4 KB
/
event.h
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#ifndef VIOLET_EVENT_H
#define VIOLET_EVENT_H
#define EVENT_KIND_NOOP 0
#define EVENT_KIND_UNDO 1
#define EVENT_KIND_REDO 2
#define EVENT_DESCRIPTION_SIZE 64
#define NAV_DESCRIPTION_SIZE 16
typedef struct event_contract {
void (*create )(void *instance, allocator_t *alc);
void (*destroy )(void *instance, allocator_t *alc);
b32 (*load )(void *instance, void *userp);
void (*save )(const void *instance, void *userp);
b32 (*execute )(void *instance);
void (*undo )(const void *instance);
void (*update )(void *dst, const void *src); // both dst and src are instances
b32 (*update_pre)(void *new, const optional(void) old); // both new and old are instances
} event_contract_t;
typedef struct event_metadata {
const event_contract_t *contract;
const u32 size;
const char description[EVENT_DESCRIPTION_SIZE];
const char *label;
/* a secondary event is one that will not be directly associated with an undo point */
const b32 secondary;
/* use VERY sparingly, i.e. when all side effects from nested events will be manually reverted
* in the root event __undo handler */
const b32 soft_merge;
const u32 version;
/* We chose to serialize most events by base64-encoding binary data in a `static_data` field
* in order to implement it for a large quantity of events in a short time. Ideally,
* we'd serialize (& version) event data at a finer-grained level. This toggle lets us migrate
* events individually over time. */
const b32 skip_static_data_serialization;
} event_metadata_t;
typedef struct event {
const event_metadata_t *meta;
array(struct event *) children;
char nav_description[NAV_DESCRIPTION_SIZE];
s64 time_since_epoch_ms;
/* expect event_kind_e */
u32 kind;
s32 status;
char instance[];
} event_t;
typedef enum event_status {
EVENT_STATUS_DONE,
EVENT_STATUS_UNDONE,
EVENT_STATUS_UNREACHABLE,
} event_status_e;
event_t *event_create(u32 kind, const event_metadata_t *meta,
const char *nav_description, allocator_t *alc);
event_t *event_create_empty(u32 kind, const event_metadata_t *meta,
const char *nav_description, allocator_t *alc);
void event_destroy(event_t *event, allocator_t *alc);
b32 event_execute(event_t *event);
void event_undo(event_t *event, allocator_t *alc);
void event_unwind_children(event_t *event, allocator_t *alc);
/* fast forward an event to another event - only used in multi-frame interactions */
void event_update(event_t *dst, const event_t *src);
/* returns true if the events are mergeable, based on the event-specific implementation */
b32 event_update_pre(event_t *dst, const event_t *src);
/* Addresses the use case when some data mutation might be repeated with a
different payload. If the desired undo behavior is such that it reverts
to the state before any of the repeated actions happened, then instead
of creating a new undo point with every transaction, the previous
transaction's undo point is modified. */
b32 event_is_multi_frame(const event_t *event);
#define event_factory(type) \
.contract = &(event_contract_t) { \
.execute = (b32 (*)(void *))event_##type##__execute, \
.undo = (void (*)(const void *))event_##type##__undo, \
}, \
.size = sizeof(event_##type##_t)
#define event_factory_multi_frame(type) \
.contract = &(event_contract_t) { \
.execute = (b32 (*)(void *))event_##type##__execute, \
.undo = (void (*)(const void *))event_##type##__undo, \
.update = (void (*)(void *, const void *))event_##type##__update, \
}, \
.size = sizeof(event_##type##_t)
#define event_factory_dynamic(type) \
.contract = &(event_contract_t) { \
.create = (void (*)(void *, allocator_t *))event_##type##__create, \
.destroy = (void (*)(void *, allocator_t *))event_##type##__destroy, \
.load = (b32 (*)(void *, void *))event_##type##__load, \
.save = (void (*)(const void *, void *))event_##type##__save, \
.execute = (b32 (*)(void *))event_##type##__execute, \
.undo = (void (*)(const void *))event_##type##__undo, \
}, \
.size = sizeof(event_##type##_t)
#define event_factory_multi_frame_dynamic(type) \
.contract = &(event_contract_t) { \
.create = (void (*)(void *, allocator_t *))event_##type##__create, \
.destroy = (void (*)(void *, allocator_t *))event_##type##__destroy, \
.load = (b32 (*)(void *, void *))event_##type##__load, \
.save = (void (*)(const void *, void *))event_##type##__save, \
.execute = (b32 (*)(void *))event_##type##__execute, \
.undo = (void (*)(const void *))event_##type##__undo, \
.update = (void (*)(void *, const void *))event_##type##__update, \
}, \
.size = sizeof(event_##type##_t)
#define event_factory_multi_frame_pre(type) \
.contract = &(event_contract_t) { \
.execute = (b32 (*)(void *))event_##type##__execute, \
.undo = (void (*)(const void *))event_##type##__undo, \
.update = (void (*)(void *, const void *))event_##type##__update, \
.update_pre = (b32 (*)(void *, const void *))event_##type##__update_pre, \
}, \
.size = sizeof(event_##type##_t)
#define event_factory_multi_frame_pre_dynamic(type) \
.contract = &(event_contract_t) { \
.create = (void (*)(void *, allocator_t *))event_##type##__create, \
.destroy = (void (*)(void *, allocator_t *))event_##type##__destroy, \
.load = (b32 (*)(void *, void *))event_##type##__load, \
.save = (void (*)(const void *, void *))event_##type##__save, \
.execute = (b32 (*)(void *))event_##type##__execute, \
.undo = (void (*)(const void *))event_##type##__undo, \
.update = (void (*)(void *, const void *))event_##type##__update, \
.update_pre = (b32 (*)(void *, const void *))event_##type##__update_pre, \
}, \
.size = sizeof(event_##type##_t)
#endif // VIOLET_EVENT_H
/* Implementation */
#ifdef EVENT_IMPLEMENTATION
event_t *event_create(u32 kind, const event_metadata_t *meta,
const char *nav_description, allocator_t *alc)
{
event_t *event = event_create_empty(kind, meta, nav_description, alc);
if (meta->contract->create)
meta->contract->create(event->instance, alc);
return event;
}
event_t *event_create_empty(u32 kind, const event_metadata_t *meta,
const char *nav_description, allocator_t *alc)
{
event_t *event = acalloc(1, sizeof(event_t) + meta->size, alc);
event->kind = kind;
event->meta = meta;
if (nav_description)
strbcpy(event->nav_description, nav_description);
else
event->nav_description[0] = 0;
event->time_since_epoch_ms = time_milliseconds_since_epoch();
event->children = array_create_ex(alc);
return event;
}
void event_destroy(event_t *event, allocator_t *alc)
{
/* recursively destroy children */
array_foreach(event->children, event_t *, child_ptr)
event_destroy(*child_ptr, alc);
array_destroy(event->children);
if (event->meta->contract->destroy)
(event->meta->contract->destroy)(event->instance, alc);
afree(event, alc);
}
b32 event_execute(event_t *event)
{
/* expect children to be created as nested events from this entry point forward */
if (!array_empty(event->children))
ASSERT_FALSE_AND_LOG("event already has children at entry point");
return (event->meta->contract->execute)(event->instance);
}
void event__unwind_children(event_t *event, allocator_t *alc)
{
/* since the side-effects from child events proceed after execution of their parent,
* reverting the side-effects requires that the children be undone first, in reverse
* insertion order */
while (!array_empty(event->children)) {
event_t *child = array_last(event->children);
event_undo(child, alc);
event_destroy(child, alc);
array_pop(event->children);
}
}
void event_undo(event_t *event, allocator_t *alc)
{
event__unwind_children(event, alc);
(event->meta->contract->undo)(event->instance);
}
void event_unwind_children(event_t *event, allocator_t *alc)
{
event__unwind_children(event, alc);
}
void event_update(event_t *dst, const event_t *src)
{
dst->time_since_epoch_ms = src->time_since_epoch_ms;
(dst->meta->contract->update)(dst->instance, src->instance);
/* NOTE(luke): no need to update children, since they will always be pruned
* upon undoing and re-created upon redoing */
}
b32 event_update_pre(event_t *dst, const event_t *src)
{
b32 result = src != NULL;
result &= dst->meta->contract->update_pre(dst->instance, src ? src->instance : NULL);
return result;
}
b32 event_is_multi_frame(const event_t *event)
{
return event->meta->contract->update != NULL;
}
#undef EVENT_IMPLEMENTATION
#endif // EVENT_IMPLEMENTATION