Appearance
aowlspt/server
Source: aowl/src/aowlspt/server.nim — 779 lines.
The high-level backend API — the server half of the pipeline.
Deliberately the same shape as aowlspt/game. A mod author moving between the two should be changing what they reach for, not how they write:
client
var Player = gameType("EFT.Player") discard Player.invoke("Heal", 50)
server
proc status(url, body, session: string): string = var o = obj() o.put("ok", true) envelope(o)
discard serve("/aowlspt/status", status)
What this wraps is route_register, db_get/db_patch and config_get. Underneath it is the same ABI the client uses, which is why one mod binary can serve both sides with a side() guard.
Types
Json
nim
Json* = object
text*: stringaowl/src/aowlspt/server.nim:36
JsonObject
nim
JsonObject* = object
parts: seq[string]aowl/src/aowlspt/server.nim:72
Handler
nim
Handler* = proc (url, body, session: string): stringaowl/src/aowlspt/server.nim:106
DbValue
nim
DbValue* = object
ok*: bool
raw*: string
error*: stringaowl/src/aowlspt/server.nim:129
ConfigValue
nim
ConfigValue* = object
ok*: bool
raw*: string
faulted*: bool
## The config file exists and did not parse, so **no** setting read
## through this mod is real -- not just this one.
##
## Appended rather than folded into `ok`, and that is the compatibility
## story: `ok` is false in this case exactly as it was before, so every
## `asText(default)` and every `if v.ok` in every mod already written
## behaves identically. What is new is that a mod which cares can now
## ask, and the ones that should care are the ones whose defaults are a
## *decision* rather than a fallback -- a load order, an enabled set, a
## list of maps. `lastError()` carries the file and the fault.aowl/src/aowlspt/server.nim:512
JsonArray
nim
JsonArray* = object
parts: seq[string]aowl/src/aowlspt/server.nim:598
Stored
nim
Stored* = object
ok*: bool
raw*: string
error*: string
missing*: bool
## True when there is no such key, false when there *is* one and it could
## not be read.
##
## The difference is not cosmetic and it is not a logging detail. A mod
## that keeps a player's progress reacts to "nothing saved yet" by making
## a new one, which is right on a first run and is the worst possible
## answer to "your save is on disk and the host could not read it": it
## writes a new character over a file that a copy out of the store's
## history would very likely have recovered. Anything that creates on a
## failed `load` must check this first and refuse loudly when it is
## false.aowl/src/aowlspt/server.nim:687
Routines
raw
nim
proc raw*(s: string): JsonText that is already JSON. The escape hatch, and the one place a mod can produce something malformed.
aowl/src/aowlspt/server.nim:39
escapeText
nim
proc escapeText*(s: string): stringaowl/src/aowlspt/server.nim:44
jstr
nim
proc jstr*(s: string): Jsonaowl/src/aowlspt/server.nim:65
jint
nim
proc jint*(i: int): Jsonaowl/src/aowlspt/server.nim:66
jfloat
nim
proc jfloat*(f: float): Jsonaowl/src/aowlspt/server.nim:67
jbool
nim
proc jbool*(b: bool): Jsonaowl/src/aowlspt/server.nim:68
jnull
nim
proc jnull*(): Jsonaowl/src/aowlspt/server.nim:69
obj
nim
proc obj*(): JsonObjectaowl/src/aowlspt/server.nim:75
put
nim
proc put*(o: var JsonObject; key: string; value: Json)aowl/src/aowlspt/server.nim:77
put
nim
proc put*(o: var JsonObject; key, value: string)aowl/src/aowlspt/server.nim:80
put
nim
proc put*(o: var JsonObject; key: string; value: int)aowl/src/aowlspt/server.nim:81
put
nim
proc put*(o: var JsonObject; key: string; value: float)aowl/src/aowlspt/server.nim:82
put
nim
proc put*(o: var JsonObject; key: string; value: bool)aowl/src/aowlspt/server.nim:83
len
nim
proc len*(o: JsonObject): intaowl/src/aowlspt/server.nim:85
done
nim
proc done*(o: JsonObject): Jsonaowl/src/aowlspt/server.nim:87
text
nim
proc text*(j: Json): stringaowl/src/aowlspt/server.nim:95
okJson
nim
proc okJson*(): stringaowl/src/aowlspt/server.nim:97
errJson
nim
proc errJson*(message: string): stringaowl/src/aowlspt/server.nim:98
serve
nim
proc serve*(url: string; handler: Handler): StatusAn exact-match route.
aowl/src/aowlspt/server.nim:108
servePrefix
nim
proc servePrefix*(prefix: string; handler: Handler): StatusA prefix route: everything under prefix reaches this handler, which reads the rest out of the url it is given. The client's endpoints carry ids in the path, so this is how most real routes are written.
aowl/src/aowlspt/server.nim:112
pathAfter
nim
proc pathAfter*(url, prefix: string): stringThe part of url past prefix, for a prefix route.
aowl/src/aowlspt/server.nim:118
dbRead
nim
proc dbRead*(path: string): DbValueReads a dotted path out of the loaded database.
aowl/src/aowlspt/server.nim:134
dbKeysReady
nim
proc dbKeysReady*(): boolWhether this host can enumerate keys.
Not a size test, because this is not an ABI entry. The probe is the sigil with no path after it: a host that implements key enumeration refuses that with ErrBadArg -- there is nothing to enumerate -- and a host that does not walks a path called "?keys ", finds no root member by that name, and answers ErrNotFound. One call, no ABI surface, and no guessing.
aowlspt-backend answers true. The simulator and the in-game client host answer false, and will until somebody adds the sigil to them; a mod should treat false as "read the subtree instead", which is what it did before this existed.
aowl/src/aowlspt/server.nim:320
dbKeys
nim
proc dbKeys*(path: string; into: var seq[string]): StatusThe immediate member names of the object at path, in document order.
The answer is the same seq[string] that keys(whole(text)) gives for the same object -- it is that call, over the array the host sends back -- so a mod can swap one for the other without changing the code that consumes it.
Four answers, and the difference between the last two is the point:
Ok--intoholds the names. An object with no members isOkand an emptyinto.ErrBadArg-- the path is an array or a scalar. A list has no keys, and it is not answered with["0","1",...]: for a large table that is a huge reply to a question whose real form is "how long is it", and a caller that wanted indices already has the length fromdbRead.ErrNotFound-- there is no such path. That is not the same as an object with no members, and a caller that treats them alike will create something over the top of a table it failed to read.ErrNotFoundagain, from a host that does not implement this at all: it looked for a root member literally named?keys <path>and did not find one. The two are indistinguishable from this call alone, which is the price of not spending an ABI revision.dbKeysReady()separates them for one call, anddbKeysOrReaddoes it for you.
What it costs
Not nothing. The reply is sum(len(name)) + 3n bytes for n members -- about 2.7 MB for a table of 100k Mongo ids. That is roughly 200x smaller than reading the subtree, and it is still megabytes crossing the ABI and then being parsed into a seq[string] on this side. Ask for it once and keep the answer for as long as you would have kept the read.
It is a snapshot, not an index. Another mod may patch that object between this call and the dbRead of a child, and the order is the document's -- which a merge can change. Nobody promised the order.
aowl/src/aowlspt/server.nim:337
dbKeysOrRead
nim
proc dbKeysOrRead*(path: string; into: var seq[string]; scanned: var bool): StatusdbKeys, falling back to reading the whole subtree on a host that has no key enumeration -- and saying which it did, in scanned.
The fallback is the read a mod was doing before this existed, and it is the expensive one: locations on a stock import is 13.5 MB and on one with loose loot it is over half a gigabyte. scanned is true when that is what happened, so a mod can log what the answer cost instead of leaving a user wondering where the second went.
ErrNotFound from this means the path is genuinely not there: the host was asked twice, the second time for the subtree itself.
aowl/src/aowlspt/server.nim:380
asText
nim
proc asText*(v: DbValue): stringaowl/src/aowlspt/server.nim:410
asFloat
nim
proc asFloat*(v: DbValue; default: float = 0.0): floataowl/src/aowlspt/server.nim:416
asInt
nim
proc asInt*(v: DbValue; default: int = 0): intaowl/src/aowlspt/server.nim:449
dbWrite
nim
proc dbWrite*(path: string; patch: Json): StatusMerges patch into the object at path, creating the path if the database has never held it.
A merge, not a replace: two mods editing sibling fields of the same item must not clobber one another. That is the difference between a mod system and a pile of mods that happen to coexist.
aowl/src/aowlspt/server.nim:486
dbWrite
nim
proc dbWrite*(path, patchJson: string): Statusaowl/src/aowlspt/server.nim:495
dbWrite
nim
proc dbWrite*(path: string; patch: JsonObject): StatusThe overload that was missing, so dbWrite(path, obj) had to be written dbWrite(path, done(obj)) -- and done(obj) at every call site is ceremony that says nothing.
aowl/src/aowlspt/server.nim:498
dbWrite
nim
proc dbWrite*(path: string; patch: JsonArray): Statusaowl/src/aowlspt/server.nim:504
setting
nim
proc setting*(key: string): ConfigValueaowl/src/aowlspt/server.nim:527
configFaulted
nim
proc configFaulted*(): boolWhether this mod's config.json is present and unreadable.
The question asked once, at load, by a mod that would rather refuse than run on defaults it did not choose. It reads the whole document, which is the cheapest way to ask -- an empty key is one round trip on every host.
aowl/src/aowlspt/server.nim:542
asText
nim
proc asText*(c: ConfigValue; default: string = ""): stringThe quotes come off here, whichever host is underneath.
They did not, and the three hosts disagreed: the backend and the client host strip them on the way out, while the simulator -- then a C# program, since rewritten in nimony -- handed back the raw JSON literal. So setting("edition").asText() was standard on a server and "standard" in the simulator -- an empty string setting had length two, and a value used to build a database path addressed nothing on one side while working on the other. Two mods had already grown their own private unquoting helper by the time it was noticed, which is the shape of a missing library function.
Stripping here rather than in each host is what makes it true everywhere, including on a host written later.
aowl/src/aowlspt/server.nim:551
asFloat
nim
proc asFloat*(c: ConfigValue; default: float = 0.0): floataowl/src/aowlspt/server.nim:572
asInt
nim
proc asInt*(c: ConfigValue; default: int = 0): intaowl/src/aowlspt/server.nim:578
asBool
nim
proc asBool*(c: ConfigValue; default: bool = false): boolaowl/src/aowlspt/server.nim:581
arr
nim
proc arr*(): JsonArrayaowl/src/aowlspt/server.nim:601
add
nim
proc add*(a: var JsonArray; value: Json)aowl/src/aowlspt/server.nim:603
add
nim
proc add*(a: var JsonArray; value: string)aowl/src/aowlspt/server.nim:604
add
nim
proc add*(a: var JsonArray; value: int)aowl/src/aowlspt/server.nim:605
add
nim
proc add*(a: var JsonArray; value: float)aowl/src/aowlspt/server.nim:606
add
nim
proc add*(a: var JsonArray; value: bool)aowl/src/aowlspt/server.nim:607
len
nim
proc len*(a: JsonArray): intaowl/src/aowlspt/server.nim:609
done
nim
proc done*(a: JsonArray): Jsonaowl/src/aowlspt/server.nim:611
put
nim
proc put*(o: var JsonObject; key: string; value: JsonArray)aowl/src/aowlspt/server.nim:619
put
nim
proc put*(o: var JsonObject; key: string; value: JsonObject)aowl/src/aowlspt/server.nim:622
add
nim
proc add*(a: var JsonArray; value: JsonObject)aowl/src/aowlspt/server.nim:625
add
nim
proc add*(a: var JsonArray; value: JsonArray)aowl/src/aowlspt/server.nim:626
objOf
nim
proc objOf*(key: string; value: Json): JsonObjectA one-member object. The client's bodies are full of single-key wrappers ({"Health":{...}}, {"Counters":[]}), and writing each as three lines of builder buries the shape of the response in ceremony.
aowl/src/aowlspt/server.nim:628
objOf
nim
proc objOf*(key: string; value: JsonObject): JsonObjectaowl/src/aowlspt/server.nim:635
objOf
nim
proc objOf*(key: string; value: JsonArray): JsonObjectaowl/src/aowlspt/server.nim:636
objOf
nim
proc objOf*(key, value: string): JsonObjectaowl/src/aowlspt/server.nim:637
objOf
nim
proc objOf*(key: string; value: int): JsonObjectaowl/src/aowlspt/server.nim:638
objOf
nim
proc objOf*(key: string; value: bool): JsonObjectaowl/src/aowlspt/server.nim:639
emptyArray
nim
proc emptyArray*(): Jsonaowl/src/aowlspt/server.nim:641
emptyObject
nim
proc emptyObject*(): Jsonaowl/src/aowlspt/server.nim:642
envelope
nim
proc envelope*(data: Json): string{"err":0,"errmsg":null,"data":...} -- what the client expects from every /client/* route.
aowl/src/aowlspt/server.nim:653
envelope
nim
proc envelope*(dataText: string): stringaowl/src/aowlspt/server.nim:658
envelope
nim
proc envelope*(o: JsonObject): stringaowl/src/aowlspt/server.nim:661
envelope
nim
proc envelope*(a: JsonArray): stringaowl/src/aowlspt/server.nim:662
envelopeNull
nim
proc envelopeNull*(): stringA successful response carrying nothing. Distinct from an empty object: some endpoints are checked for data === null by the client.
aowl/src/aowlspt/server.nim:664
failure
nim
proc failure*(code: int; message: string): stringThe error shape. code is the client's own error number; zero would say success, so it is refused rather than sent.
aowl/src/aowlspt/server.nim:669
save
nim
proc save*(key: string; value: Json): StatusWrite this mod's value at key, durably. Keys are flat and [A-Za-z0-9._-]; anything else is refused rather than mangled into a filename that might collide with another key.
aowl/src/aowlspt/server.nim:704
save
nim
proc save*(key, value: string): Statusaowl/src/aowlspt/server.nim:710
save
nim
proc save*(key: string; value: JsonObject): Statusaowl/src/aowlspt/server.nim:713
load
nim
proc load*(key: string): StoredRead this mod's value at key. ok is false and error says why when it was never written -- which is the normal first-run case, not a fault -- and missing separates that case from a value that is there and unreadable.
aowl/src/aowlspt/server.nim:715
saved
nim
proc saved*(key: string): boolaowl/src/aowlspt/server.nim:727
savedKeys
nim
proc savedKeys*(prefix: string = ""): seq[string]This mod's keys beginning with prefix, so "every profile" is expressible without the mod knowing where the host put them.
aowl/src/aowlspt/server.nim:731
broadcast
nim
proc broadcast*(name: string; payload: Json): StatusTell every other mod something happened. The emitter does not receive its own event: a mod that owns an event usually also subscribes to it, and delivering it back would be a loop nobody wrote.
aowl/src/aowlspt/server.nim:755
broadcast
nim
proc broadcast*(name, payload: string): Statusaowl/src/aowlspt/server.nim:761
broadcast
nim
proc broadcast*(name: string; payload: JsonObject): Statusaowl/src/aowlspt/server.nim:762
broadcast
nim
proc broadcast*(name: string; payload: JsonArray): Statusaowl/src/aowlspt/server.nim:764
onEvent
nim
proc onEvent*(name: string; handler: EventHandler): Statusaowl/src/aowlspt/server.nim:767
afterMs
nim
proc afterMs*(delayMs: int; handler: TickHandler): StatusRun handler once, delayMs from now, from the server's own loop rather than from a request thread.
aowl/src/aowlspt/server.nim:770
everyMs
nim
proc everyMs*(intervalMs: int; handler: TickHandler): StatusRun handler on a repeating timer, off the request threads. What a periodic sweep -- expiring offers, posting insurance returns -- is for.
aowl/src/aowlspt/server.nim:775

