1.1.4 • Published 6 days ago

@teamteanpm2024/dolores-vitae-eum v1.1.4

Weekly downloads
-
License
MIT
Repository
github
Last release
6 days ago

Defining Schema

As Colyseus is written in TypeScript, the schema is defined as type annotations inside the state class. Additional server logic may be added to that class, but client-side generated (not implemented) files will consider only the schema itself.

import { Schema, type, ArraySchema, MapSchema } from '@teamteanpm2024/dolores-vitae-eum';

export class Player extends Schema {
  @type("string")
  name: string;

  @type("number")
  x: number;

  @type("number")
  y: number;
}

export class State extends Schema {
  @type('string')
  fieldString: string;

  @type('number') // varint
  fieldNumber: number;

  @type(Player)
  player: Player;

  @type([ Player ])
  arrayOfPlayers: ArraySchema<Player>;

  @type({ map: Player })
  mapOfPlayers: MapSchema<Player>;
}

See example.

Supported types

Primitive Types

TypeDescriptionLimitation
stringutf8 stringsmaximum byte size of 4294967295
numberauto-detects int or float type. (extra byte on output)0 to 18446744073709551615
booleantrue or false0 or 1
int8signed 8-bit integer-128 to 127
uint8unsigned 8-bit integer0 to 255
int16signed 16-bit integer-32768 to 32767
uint16unsigned 16-bit integer0 to 65535
int32signed 32-bit integer-2147483648 to 2147483647
uint32unsigned 32-bit integer0 to 4294967295
int64signed 64-bit integer-9223372036854775808 to 9223372036854775807
uint64unsigned 64-bit integer0 to 18446744073709551615
float32single-precision floating-point number-3.40282347e+38 to 3.40282347e+38
float64double-precision floating-point number-1.7976931348623157e+308 to 1.7976931348623157e+308

Declaration:

Primitive types (string, number, boolean, etc)

@type("string")
name: string;

@type("int32")
name: number;

Custom Schema type

@type(Player)
player: Player;

Array of custom Schema type

@type([ Player ])
arrayOfPlayers: ArraySchema<Player>;

Array of a primitive type

You can't mix types inside arrays.

@type([ "number" ])
arrayOfNumbers: ArraySchema<number>;

@type([ "string" ])
arrayOfStrings: ArraySchema<string>;

Map of custom Schema type

@type({ map: Player })
mapOfPlayers: MapSchema<Player>;

Map of a primitive type

You can't mix types inside maps.

@type({ map: "number" })
mapOfNumbers: MapSchema<number>;

@type({ map: "string" })
mapOfStrings: MapSchema<string>;

Backwards/forwards compability

Backwards/fowards compatibility is possible by declaring new fields at the end of existing structures, and earlier declarations to not be removed, but be marked @deprecated() when needed.

This is particularly useful for native-compiled targets, such as C#, C++, Haxe, etc - where the client-side can potentially not have the most up-to-date version of the schema definitions.

Reflection

The Schema definitions can encode itself through Reflection. You can have the definition implementation in the server-side, and just send the encoded reflection to the client-side, for example:

import { Schema, type, Reflection } from "@teamteanpm2024/dolores-vitae-eum";

class MyState extends Schema {
  @type("string")
  currentTurn: string;

  // more definitions relating to more Schema types.
}

// send `encodedStateSchema` across the network
const encodedStateSchema = Reflection.encode(new MyState());

// instantiate `MyState` in the client-side, without having its definition:
const myState = Reflection.decode(encodedStateSchema);

Data filters

On the example below, considering we're making a card game, we are filtering the cards to be available only for the owner of the cards, or if the card has been flagged as "revealed".

import { Schema, type, filter } from "@teamteanpm2024/dolores-vitae-eum";

export class State extends Schema {
  @filterChildren(function(client: any, key: string, value: Card, root: State) {
      return (value.ownerId === client.sessionId) || value.revealed;
  })
  @type({ map: Card })
  cards = new MapSchema<Card>();
}

Limitations and best practices

  • Each Schema structure can hold up to 64 fields. If you need more fields, use nested structures.
  • NaN or null numbers are encoded as 0
  • null strings are encoded as ""
  • Infinity numbers are encoded as Number.MAX_SAFE_INTEGER
  • Multi-dimensional arrays are not supported.
  • Items inside Arrays and Maps must be all instance of the same type.
  • @teamteanpm2024/dolores-vitae-eum encodes only field values in the specified order.
    • Both encoder (server) and decoder (client) must have same schema definition.
    • The order of the fields must be the same.
  • Avoid manipulating indexes of an array. This result in at least 2 extra bytes for each index change. Example: If you have an array of 20 items, and remove the first item (through shift()) this means 38 extra bytes to be serialized.

Generating client-side schema files (for strictly typed languages)

If you're using JavaScript or LUA, there's no need to bother about this. Interpreted programming languages are able to re-build the Schema locally through the use of Reflection.

You can generate the client-side schema files based on the TypeScript schema definitions automatically.

# C#/Unity
schema-codegen ./schemas/State.ts --output ./unity-project/ --csharp

# C/C++
schema-codegen ./schemas/State.ts --output ./cpp-project/ --cpp

# Haxe
schema-codegen ./schemas/State.ts --output ./haxe-project/ --haxe

Benchmarks:

Scenario@teamteanpm2024/dolores-vitae-eummsgpack + fossil-delta
Initial state size (100 entities)26713283
Updating x/y of 1 entity after initial state926
Updating x/y of 50 entities after initial state342684
Updating x/y of 100 entities after initial state6681529

Decoder implementations

Decoders for each target language are located at /decoders/. They have no third party dependencies.

Why

Initial thoghts/assumptions, for Colyseus:

  • little to no bottleneck for detecting state changes.
  • have a schema definition on both server and client
  • better experience on staticaly-typed languages (C#, C++)
  • mutations should be cheap.

Practical Colyseus issues this should solve:

  • Avoid decoding large objects that haven't been patched
  • Allow to send different patches for each client
  • Better developer experience on statically-typed languages

Inspiration:

License

MIT

hashes-shimsshebangRegExp.prototype.flagssameValueZeroimmerextraES2021languageschemeoptimistmulti-packagecss nestingcolorcompilerreadablecurlfpcompile lesschinesermsymboldom-testing-librarycallbackrapidfindLastIndexfantasy-landes5package managermimetoolkittrimgenericsutilwatchingassertsprototypeUint8Arraymapreduceequalityperformantclassnameterminalslicefull-widthdebuggerpackage.jsonparentfast-clonechecknativextermReactiveXclassnameswalkingspinnerkarmacors6to5ECMAScript 5Uint8ClampedArraygesturesphonesymlinkstypaniondebugavaanimationyamlruntimeasciixssttytraverseUnderscoreES2020ansiaccessibilitytimequeueMicrotaskajaxemojideep-copystringifierponyfillcss lessRxreduxbyteLengthfindLastsymbolsredux-toolkitdirrmdirreact-testing-librarystabletoolstaskArray.prototype.flatMapmergenumberdeep-clonejsperformancemkdirpjsxasttrimLefttypesafereact-hooksmake dirjavascripttypedlibphonenumberpushECMAScript 2019jsonschemadescriptorglacierformvieweast-asian-widthspecmacostoSortedmatchespreserve-symlinksObject.keysirqfind-upfile systemstarterencryptionobjSymbol.toStringTagio-tsemitcommandWeakSetautoscalingreact poseimmutablespinnersStreamsexit-codeESnextarraybufferECMAScript 6keysarktypees3dtacitURLSearchParamserrorbeanstalkfastclonevariablesregularvariables in cssdotenvArray.prototype.includesmatchAllvisualreuseparsinghooksArray.prototype.flattennameslimited0scheme-validationrfc4122argparseES2016class-validatorutil.inspectgradients css3modulesworkerduplexfastopenhttpwafObject.valuesfullwidthserializationECMAScript 3balancedsetImmediatefolderregexenderdeletesideMicrosoftdeepcloneconcatinterruptsspeedstylingfunctionalArray.prototype.findLastshellexecfigletdependenciesshamelasticachereal-timeobjectmiddlewarerm -frcryptofunctionvestnegativewebenumerablehardlinksregular expressionECMAScript 7takereadcloneresolve@@toStringTagbatchbusyECMAScript 2022npmFloat32ArraydefinePropertysortedcolumnscallJSONsimpledbdropproxysettingsoffsetmkdires2018react animationpicomatchpostcsswalksanitizationtypescripthookformregexpyuplessrandompostcss-pluginunicodeTypeScripthasOwnnested cssstreamES7schemacrypthasOwnPropertytelephonemonorepometadatapyyamlreducerbootstrap lessrequestextensionestreeartindicatorlintsequenceparserprotoidlecharactersstylemkdirspersistentECMAScript 2015less compilerthroattddroute53widthlesscssgetoptratelimittypeoflastwatchFileJSON-Schemargbquerylimittyped arraygetintrinsicramdacolorsfixed-widthIteratorelectrona11yMapequalsuperagentebshigher-orderpipespringstoragegatewayfromoptioncurriedYAMLpathcss-in-jsObject.assigninecmascript_.extendintrinsicredactpackagesomitcolourpackagetypedarraysinstalluser-streamsstatusincludesthreeasyncconnectsesbrowserslistbddArrayhandlersreadablestreamcloudwatchflagsyntaxbundlingiteratorcall-bindwritableinferencebrowserlisterror-handlingdescriptorsapipromisethrottlequeueSymbolwrapwarninglockfilelookchaiagentbcryptcallboundcloudformationfastcopynopeESTypeBoxBigInt64ArrayES3s3Int16ArraydiffWebSocketdeepcopyRxJSdayjsargumentawssafefetchbannerworkflowsetPrototypeOfcircularless cssupmapcode pointsparentsObject.isvalueserializerfastifysuperstructownlistenerstranspileswfcollectionisConcatSpreadablejsdomtestpurees-abstractes7es-shim APIjson-schema-validatorhas-ownbrowserdatastructureletArrayBuffer.prototype.sliceconsumeless mixinsawaitcodesconcurrencyjson-schema-validationwindowsprotocol-buffersUint32Array-0typestslibObject.entriesweakmapdataviewcharacterES5efficientconfigjwtreactstringFunction.prototype.nameRegExp#flagssetterECMAScript 2018es2017inspectcjktouchassignAsyncIteratorgroupByECMAScript 2023rdses8validationdeterministicwordwraprequirereduceprotobufforEachsanitizePushvpcdatautility$.extendwgetnodejsreact-hook-formmochaBigUint64Arrayflatkoreanfast-deep-copyCSSStyleDeclarationprocesslazyeslintconfigpromisesboundES8valuesajvcloudtrailfindupinternallogtoobjectpluginpropertiesframeworkgdprpropertypoint-freeeventDispatcherbuffersglobhelpersFloat64ArraytesterparseoncejQueryguidlinewrapprefixtoArraydependency managermimetypesarraysfullcomparepnpm9core-jsautoprefixerlengthloadingimportexportflattenfseventsiterationsymlinkpolyfillObjectArrayBuffer#slicetypedarraytestingqsjsonjsonpathlinuxsignalspasswordstructuredClonesomeHyBitrimStartES2019eventEmitterWeakMapdeepjapaneseURLposefast-copygetfpsrateformattingdynamodbharmonyrobuststyled-componentscommand-linestreams2groupObject.getPrototypeOfObject.fromEntriesES2015ES2017stringifytapeTypedArraydefinePromisepatchgetOwnPropertyDescriptorArrayBufferdirectoryoutputstyleswhichwaapicomputed-typescloudsearchelblrues6css.envObject.definePropertyes2016String.prototype.matchAllconsoleescapeextendcopybundlerregular expressionsReactiveExtensionsjsdiffES2018look-upshimrecursivewaitsyntaxerrortostringtagslotArray.prototype.containsminimalcoreString.prototype.trimremovepropxhrbreakarraytexttranspilermovees2015Uint16ArrayurldataViewwatchObservables__proto__bluebirdsigtermcacheECMAScript 2021prunetypeiterateenvbyteOffsetoptimizerl10nvalidatorwordbreakhttpsec2flagsjasminesqsjestloadbalancingcensoreverysharedstatelessgetterbabelStyleSheetform-validationstyleguideuninstallassertcommanderbootstrap cssjson-schemaaccessoreslintstreamsconcatMapReflect.getPrototypeOfairbnbcollection.es6watcherinstallerECMAScript 2016momentvartoStringTagpreprocessorlinksnsentriestc39utilitiestrimRightsetdescriptionfunctionsbindtapquerystringArray.prototype.flateslint-plugin
1.1.4

6 days ago

1.1.3

7 days ago

1.0.3

8 days ago

1.0.2

9 days ago

1.0.1

10 days ago

1.0.0

11 days ago