This is an empty base worker, ready for you to add your game's specific logic to it. It is intended to be added to an existing SpatialOS project and as such, does not include SpatialOS configuration files and only an empty schema.
This is a preview, and the APIs and other aspects of this project can and will change based on your feedback.
It is assumed that you are moderately familiar with the C# language, as well as SpatialOS and its concepts.
The C# worker APIs encourage the use of Tasks
and other parallel constructs, so thread safety is a major concern.
To aid with this:
- Schema
types
andcomponents
are generated asreadonly
structs. list<>
andmap<>
fields are represented byImmutableArray<>
andImmutableDictionary<>
.option<>
fields are represented asSystem.Nullable<>
.
There are also a few helpers available:
Improbable.Stdlib.ComponentCollection
is a thread-safe view of the worker's checked out entity components.Improbable.Stdlib.WorkerConnection
is a thread-safe wrapper around the low-level SpatialOSConnection
object. It enables the use ofasync/await
constructs when sending SpatialOS command requests.Improbable.Stdlib.OpList
is a wrapper around the low-level SpatialOSOpList
. It enables the construction of LINQ queries, and includes op-specific helpers likeOfOpType<>
andOfComponent
.
- Install .NET Core v3.1 (SDK and Runtime) from the download archive for your system.
CSharpCodeGenerator
- A schemalang-to-C# code generator. This can also be used as a basis for making a code generator for other languages.GeneratedCode
- The output of theCSharpCodeGenerator
.Improbable
- The source for supporting libraries meant to be used by your worker. Once APIs have finalized, these will also be available on nuget.orgschema
- A placeholder, empty schema, so the project builds out of the box.scripts
- Scripts that automate common and repetitive tasks.Workers
- Your worker(s).
We currently build local Nuget packages of our supporting libraries. The Nuget.config in the root directory allows nuget to resolve these packages from the local filesystem.
Windows
scripts/build-nuget-packages.ps1
macOS/Linux
scripts/build-nuget-packages.sh
You will need to re-run this if you make changes to the projects in the
Improbable
directory, or if you delete thenupkgs
directory in the root of this project.
This project contains a simple blank schema file to ensure everything compiles immediately after you've checked it out.
To point the code generator to your project's schema, do the following:
- Open
_root_directory_/Directory.Build.props
- Find the
SchemaInputDir
element. By default it looks like this:
<SchemaInputDir Include="$(SolutionDir)\schema" />
- Change it to point to your project's
schema
directory, for example:
<SchemaInputDir Include="$(SolutionDir)\..\SpatialOS\schema" />
Note that depending on your target project, you may need more than one schema input directory.
You can specify multiple schema input directories by separating each one with a ;
.
Once you've done this, you can delete the placeholder schema
directory.
- Open the solution in JetBrains Rider, Visual Studio, or Visual Studio Code and select and build the
x64
platform. - Or, build directly using
dotnet build Workers.sln -p:Platform=x64
.
If you want to explore the available command line options, you can run the worker:
dotnet run -p Workers/GameLogic -- help
Build your managed workers for the cloud by running:
Windows
`scripts/publish-linux-worker.ps1`
macOS/Linux
`scripts/publish-linux-worker.sh`
Improbable.Stdlib.ComponentCollection
provides a way to keep track of components that are visible to your worker.
Each type of component has a static CreateComponentCollection
method.
private readonly ComponentCollection<EntityAcl> acls = EntityAcl.CreateComponentCollection();
Whenever you process an OpList, call ProcessOpList
so the collection can keep up-to-date.
acls.ProcessOpList(opList);
Then access the component data using Get
or TryGet
.
var component = acls.Get(entityId);
Each component defines an Update
type which can be used to build an update.
Call update.ToSchemaUpdate()
to copy the data to SpatialOS.
var update = new DatabaseSyncService.Update();
update.AddPathsUpdatedEvent(new PathsUpdated(changedPaths));
DatabaseSyncService.SendUpdate(connection, tagretEntityId, update);
Improbable.Stdlib.OpList
allows for using LINQ methods to filter the ops that your worker is concerned with.
Process only AddEntityOp
:
var addEntityOps = opList.OfOpType<AddEntityOp>();
Process only component updates for a specific component:
.OfOpType<ComponentUpdateOp>()
.OfComponent(EntityAcl.ComponentId);
Each component that defines commands provides static methods of the form Task<ResponseType> Send<CommandName>Async(WorkerConnection, RequestType, ...);
var children = await DatabaseSyncService.SendGetItemsAsync(connection, new GetItemsRequest(profileId, GetItemDepth.Recursive, connection.WorkerId));
Each component that defines commands provides a Commands
enum. You can use this with LINQ queries to filter to a specific command type.
var ops = opList
.OfOpType<CommandRequestOp>()
.OfComponent(DatabaseSyncService.ComponentId)
.Where(op => DatabaseSyncService.GetCommandType(op) == DatabaseSyncService.Commands.Create);
Each component that defines commands provides static methods of the form void Send<CommandName>Response(WorkerConnection, ResponseId, ResponseType, ...);
DatabaseSyncService.SendGetItemsResponse(connection, commandRequestOp.RequestId, new GetItemsResponse(children));
connection.SendCommandFailure(commandRequestOp.RequestId, "Error message");
var updateOps = opList
.OfOpType<ComponentUpdateOp>()
.OfComponent(MyComponent.ComponentId)
.Select(updateOp => updateOp.EntityId);
foreach(var entityId in updateOps)
{
Log.Information("Updated entity {EntityId}", entityId);
}
if (opList.TryGetWorkerFlagChange(key, ref newValue))
{
flagValues[key] = newValue;
}
- Changes to
.schema
files won't always cause theGeneratedCode
project to rebuild when open in Visual Studio. Manually "Rebuild" theGeneratedCode
project to work around this.
This software is licensed under MIT. See the LICENSE file for details.
We currently don't accept PRs from external contributors - sorry about that! We do accept bug reports and feature requests in the form of issues, though.