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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
|
#include "app.h"
#include "../../connections/store-port.h"
#include "../../connections/store.h"
#include <stdlib.h>
#include <string.h>
#include <float.h>
_Static_assert(sizeof(double) == 8 && FLT_RADIX == 2 && DBL_MANT_DIG == 53 && DBL_MAX_EXP == 1024,
"the current snapshot boundary requires IEEE binary64");
static const PureProgram connections = {.read = {.to_end = true}, .write = {.to_end = true}};
static const unsigned char marker[8] = {'P','U','R','E','S','0','0','1'};
enum { SNAPSHOT_HEADER = 44 };
static void release(void *memory, size_t size) { (void)size; free(memory); }
static bool allocate(void *context, size_t size, PureResource *out) {
(void)context;
void *memory = malloc(size);
if (!memory) return false;
*out = (PureResource){.body = {memory, size}, .allocation = memory,
.allocation_size = size, .release = release};
return true;
}
static uint64_t decode(const unsigned char *bytes, unsigned count) {
uint64_t value = 0;
for (unsigned i = 0; i < count; ++i) value |= (uint64_t)bytes[i] << (8 * i);
return value;
}
static void encode(unsigned char *bytes, uint64_t value, unsigned count) {
for (unsigned i = 0; i < count; ++i) bytes[i] = (unsigned char)(value >> (8 * i));
}
static PureResult read_value(void *context, const void *definition, const char *key,
size_t available, PureResource *out, int64_t *revision) {
PureNativeApp *app = context;
(void)definition;
if (!strcmp(key, "load")) {
PureHost store = pure_store_host(app->directory);
return store.read(store.context, &connections, app->document, available, out, revision);
}
if (strcmp(key, "snapshot")) return PURE_INVALID;
if (app->held) return PURE_BUSY;
size_t size = SNAPSHOT_HEADER + 8u * app->plan->state_count;
/* This host contract currently exports the complete typed snapshot. */
if (size > available) return PURE_BUDGET;
if (!allocate(NULL, size, out)) return PURE_ALLOCATION;
unsigned char *bytes = out->allocation;
memcpy(bytes, marker, sizeof(marker));
memcpy(bytes + 8, app->plan->identity, 32);
encode(bytes + 40, app->plan->state_count, 4);
for (uint32_t i = 0; i < app->plan->state_count; ++i) {
double value = pure_scalar_get_state(app->scalar, i);
uint64_t bits;
memcpy(&bits, &value, sizeof(bits));
encode(bytes + SNAPSHOT_HEADER + i * 8, bits, 8);
}
*revision = app->revision;
return PURE_OK;
}
static PureResult action(PureNativeApp *app, PureAction a) {
if (a.phase == PURE_RESET) {
if (app->held) return PURE_BUSY;
pure_scalar_reset_state(app->scalar);
return PURE_OK;
}
if (a.phase == PURE_FRAME) {
app->last_scalar_error = pure_scalar_dispatch(app->scalar, 0, 1.0 / 60.0);
return app->last_scalar_error ? PURE_INVALID : PURE_OK;
}
if (a.input >= app->plan->input_count || a.input >= 8) return PURE_INVALID;
PureScalarInputKind kind = app->plan->inputs[a.input].kind;
uint32_t bit = 1u << a.input, event;
if (kind == PURE_VALUE) {
if (a.phase != PURE_CHANGE) return PURE_INVALID;
app->last_scalar_error = pure_scalar_dispatch_value(app->scalar, 33 + a.input, a.x);
return app->last_scalar_error ? PURE_INVALID : PURE_OK;
}
if (kind == PURE_KEY) {
if ((a.phase != PURE_DOWN && a.phase != PURE_UP) || a.source >= 32) return PURE_INVALID;
uint32_t source = 1u << a.source, before = app->key_sources[a.input];
if (a.phase == PURE_DOWN) {
app->key_sources[a.input] |= source;
app->held |= bit;
if (before) return PURE_OK;
} else {
if (!(before & source)) return PURE_OK;
app->key_sources[a.input] &= ~source;
if (app->key_sources[a.input]) return PURE_OK;
app->held &= ~bit;
}
event = a.input * 2 + (a.phase == PURE_DOWN ? 1u : 2u);
app->last_scalar_error = pure_scalar_dispatch(app->scalar, event, 0);
} else {
if (a.phase > PURE_CANCEL) return PURE_INVALID;
uint32_t slot = a.input;
if (kind == PURE_ROUTED) {
if (a.target >= app->plan->target_count || a.target >= 24 || app->plan->targets[a.target].input != a.input) return PURE_INVALID;
slot = 8 + a.target;
}
bit = 1u << slot;
if (a.phase == PURE_DOWN) {
if (app->held & bit) return app->pointer_owners[slot] == a.source ? PURE_OK : PURE_BUSY;
app->held |= bit;
app->pointer_owners[slot] = a.source;
} else {
if (!(app->held & bit) || app->pointer_owners[slot] != a.source) return PURE_INVALID;
if (a.phase == PURE_UP || a.phase == PURE_CANCEL) app->held &= ~bit;
}
event = a.phase == PURE_MOVE ? 17 + a.input : a.phase == PURE_CANCEL ? 25 + a.input
: a.input * 2 + (a.phase == PURE_DOWN ? 1u : 2u);
app->last_scalar_error = kind == PURE_ROUTED
? pure_scalar_dispatch_target(app->scalar, event, a.target, a.x, a.y)
: pure_scalar_dispatch_point(app->scalar, event, a.x, a.y);
}
/* Input ownership follows the physical phases even if an authored handler
* fails; its scalar writes remain atomic. Each routed target has its owner. */
return app->last_scalar_error ? PURE_INVALID : PURE_OK;
}
static PureResult write_value(void *context, const void *definition, const char *key,
int64_t expected, PureSpan bytes, PureSpan *reply,
int64_t *revision) {
PureNativeApp *app = context;
(void)definition;
*reply = (PureSpan){0};
if (!strcmp(key, "save")) return pure_store_put(app->directory, app->document, expected, bytes, revision);
*revision = app->revision;
if (expected != app->revision) return PURE_CONFLICT;
if (app->revision == INT64_MAX) return PURE_LIMIT;
app->last_scalar_error = 0;
if (!strcmp(key, "event")) {
if (bytes.size != sizeof(PureAction)) return PURE_LIMIT;
PureAction input;
memcpy(&input, bytes.data, sizeof(input));
PureResult status = action(app, input);
if (status != PURE_OK) return status;
#ifdef PURE_NATIVE_AUTHORING
} else if (!strcmp(key, "plan")) {
if (bytes.size != sizeof(const PureScalarPlan *)) return PURE_LIMIT;
if (app->held) return PURE_BUSY;
const PureScalarPlan *candidate;
memcpy(&candidate, bytes.data, sizeof(candidate));
if (!candidate || !candidate->states || !candidate->inputs || !candidate->views ||
candidate->state_count > 32 || candidate->input_count > 8 ||
candidate->target_count > 24 || candidate->view_count > 24) return PURE_INVALID;
app->last_scalar_error = pure_scalar_load(app->scalar, candidate, app->plan, NULL);
if (app->last_scalar_error) return PURE_INVALID;
app->plan = candidate;
memset(app->pointer_owners, 0, sizeof(app->pointer_owners));
memset(app->key_sources, 0, sizeof(app->key_sources));
#endif
} else if (!strcmp(key, "restore")) {
if (app->held) return PURE_BUSY;
if (bytes.size != SNAPSHOT_HEADER + 8u * app->plan->state_count ||
memcmp(bytes.data, marker, sizeof(marker)) || memcmp(bytes.data + 8, app->plan->identity, 32) ||
decode(bytes.data + 40, 4) != app->plan->state_count) return PURE_INVALID;
double values[32];
for (uint32_t i = 0; i < app->plan->state_count; ++i) {
uint64_t bits = decode(bytes.data + SNAPSHOT_HEADER + 8 * i, 8);
memcpy(values + i, &bits, sizeof(bits));
}
app->last_scalar_error = pure_scalar_load(app->scalar, app->plan, NULL, values);
if (app->last_scalar_error) return PURE_INVALID;
} else return PURE_INVALID;
*revision = ++app->revision;
return PURE_OK;
}
PureResult pure_native_app_init(PureNativeApp *app, const PureScalarPlan *plan, void *memory,
size_t size, const char *directory, const char *key, size_t budget) {
if (!app || !plan || !directory || !key || !*key || strlen(key) > 64 || plan->state_count > 32) return PURE_INVALID;
for (const char *p = key; *p; ++p)
if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
(*p >= '0' && *p <= '9') || *p == '_' || *p == '-')) return PURE_INVALID;
PureScalar *scalar = pure_scalar_initialize(memory, size);
if (!scalar) return PURE_INVALID;
if (pure_scalar_load(scalar, plan, NULL, NULL)) return PURE_INVALID;
PureResult status = pure_store_init(directory);
if (status != PURE_OK) return status;
*app = (PureNativeApp){.scalar = scalar, .plan = plan, .directory = directory};
strcpy(app->document, key);
PureHost host = {.context = app, .allocate = allocate, .read = read_value, .write = write_value};
return pure_runtime_init(&app->execution, NULL, 0, NULL, host, budget) ? PURE_OK : PURE_INVALID;
}
static PureOutcome finish(PureNativeApp *app, PureTicket ticket) {
PureOutcome result;
while (!pure_runtime_result(ticket, &result)) {
if (!pure_runtime_step(&app->execution)) return (PureOutcome){.status = PURE_INVALID};
}
return result;
}
PureOutcome pure_native_app_action(PureNativeApp *app, PureAction input) {
PureWork work[1];
if (!pure_runtime_bind(&app->execution, work, 1))
return (PureOutcome){.status = PURE_BUSY};
PureTicket ticket;
PureResult status = pure_runtime_accept(&app->execution, PURE_INPUT_WRITE, "event", app->revision, sizeof(input), &ticket);
PureOutcome result = {.status = status};
if (status == PURE_OK) {
memcpy(pure_runtime_ingress(ticket, NULL), &input, sizeof(input));
pure_runtime_ready(ticket);
result = finish(app, ticket);
pure_runtime_release(ticket);
}
result.body = (PureSpan){0};
if (!pure_runtime_bind(&app->execution, NULL, 0)) abort();
return result;
}
static PureOutcome transfer(PureNativeApp *app, bool restore) {
PureWork work[2];
if (!pure_runtime_bind(&app->execution, work, 2))
return (PureOutcome){.status = PURE_BUSY};
PureTicket source, destination;
PureResult status = pure_runtime_accept(&app->execution, PURE_INPUT_READ, restore ? "load" : "snapshot", 0, 0, &source);
PureOutcome result = {.status = status};
if (status != PURE_OK) goto done;
result = finish(app, source);
int64_t stored = result.revision;
if (result.status == PURE_OK) {
status = pure_runtime_forward(source, &app->execution, restore ? "restore" : "save",
restore ? app->revision : app->stored_revision, &destination);
pure_runtime_release(source);
if (status != PURE_OK) { result = (PureOutcome){.status = status}; goto done; }
result = finish(app, destination);
pure_runtime_release(destination);
if (result.status == PURE_OK) app->stored_revision = restore ? stored : result.revision;
} else pure_runtime_release(source);
done:
/* Bodies are consumed internally before this API returns. */
result.body = (PureSpan){0};
if (!pure_runtime_bind(&app->execution, NULL, 0)) abort();
return result;
}
PureOutcome pure_native_app_save(PureNativeApp *app) { return transfer(app, false); }
PureOutcome pure_native_app_restore(PureNativeApp *app) { return transfer(app, true); }
#ifdef PURE_NATIVE_AUTHORING
PureResult pure_native_app_replace_plan(PureNativeApp *app, const PureScalarPlan *candidate) {
if (!app || !candidate) return PURE_INVALID;
if (app->held || app->execution.active || app->execution.retained) return PURE_BUSY;
PureWork work[1];
if (!pure_runtime_bind(&app->execution, work, 1)) return PURE_BUSY;
PureTicket ticket;
PureResult status = pure_runtime_accept(&app->execution, PURE_INPUT_WRITE,
"plan", app->revision, sizeof(candidate), &ticket);
if (status == PURE_OK) {
memcpy(pure_runtime_ingress(ticket, NULL), &candidate, sizeof(candidate));
pure_runtime_ready(ticket);
PureOutcome result = finish(app, ticket);
pure_runtime_release(ticket);
status = result.status;
}
if (!pure_runtime_bind(&app->execution, NULL, 0)) abort();
return status;
}
#endif
|