Giter VIP home page Giter VIP logo

spatialos-sdk-rs's Introduction

SpatialOS SDK for Rust

An integration of the SpatialOS C API and the Rust programming language.

Note: This is an unofficial integration of the SpatialOS C API with Rust. Improbable does not officially support Rust as a worker language.

Quick start

Requirements

To develop in this repository you'll need:

  1. Rust v1.40
  2. A SpatialOS account
  3. The Spatial CLI installed and available on your PATH.

Setup

  1. Clone this repository.

  2. Install cargo-spatial.

    $ cargo install --path ./cargo-spatial --force
    
  3. Set the SPATIAL_LIB_DIR environment variable to the location of the SpatialOS dependencies.

    $ export SPATIAL_LIB_DIR=$(pwd)/dependencies
    
  4. Download the C API dependencies.

    $ cargo spatial download sdk --sdk-version 14.8.0 --with-test-schema
    
  5. Build the spatialos-sdk crate.

    $ cd spatialos-sdk && cargo build
    

At this point, the spatialos-sdk crate has been built and linked successfully and can be used in user code.

Running the Example Project

To run the example project, you will need to:

  1. Navigate to the example project:
$ cd project-example
  1. Generate Rust code from the project's SpatialOS schema.
$ cargo spatial codegen
  1. Launch a local deployment:
$ cargo spatial local launch

Its ready to go when you see a message like the following in your console.

Access the Inspector at http://localhost:21000/inspector

Navigate to http://localhost:21000/inspector in your browser to view your deployment. You should see 4 workers already connected, these were started by the SpatialOS Runtime.

If you want to manually launch another instance of the worker, run the following command from the project-example directory:

$ cargo run -- --worker-id RustWorker999 --worker-type RustWorker receptionist

This will allow you to see the log output of the worker as it runs.

Running the test-suite

There are some integration tests that live in the test-suite crate. These utilize and test generated code. To run these tests:

$ cd test-suite && cargo spatial codegen && cargo test

Testing the code generator

To regenerate the schema bundle, run the following:

$ ./dependencies/schema-compiler/schema_compiler --schema_path=project-example/schema --schema_path=dependencies/std-lib project-example/schema/example.schema --bundle_json_out=spatialos-sdk-code-generator/data/test.sb.json

To run the code generator tests, run the following:

$ cargo test -p spatialos-sdk-code-generator

To display Rusts auto-generated debug representation of the schema bundle, run the following:

$ cargo test -p spatialos-sdk-code-generator -- --nocapture

Updating Rust bindings

To update the Rust bindings found in spatialos-sdk-sys run the following command from the root of the repository:

$ cargo run --bin generate_bindings -- -i ./dependencies/headers/include/improbable/ -o ./spatialos-sdk-sys/src/

Note: this depends on bindgen which has clang as a dependency. See bindgen's documentation for more info.

License

Licensed under either of Apache License, Version 2.0 or MIT at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

spatialos-sdk-rs's People

Contributors

dependabot-preview[bot] avatar dgavedissian avatar jacmob avatar jamiebrynes7 avatar johnpmayer avatar randompoison avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

spatialos-sdk-rs's Issues

Only generate schema bindings for the local crate

The current method for code generation involves generating the binding code for all schema-defined types in a single Rust source file. While this approach is fairly simple, it has the major drawback of making it difficult (or impossible) to distribute schema files and schema-generated code as part of a crate. For example, the SDK can't provide any useful method or trait implementations for the improbable::Position component, since the component effectively doesn't exist until the end-user generates it.

An alternative approach would be for each crate to distribute the generated code associated with any schema types it defines. This would allow the crate to also provide useful inherent/trait impls for such types. The SDK would distribute the standard schema library.

Unanswered Questions

  • How do crates distribute the schema files themselves (i.e. is it even possible to distribute non-Rust assets in a crate)?
  • How can we have the schema compiler find schema files pulled in from third-party crates?
    • cargo_metadata provides a way to list all crates being built as part of a project. We can search all dependencies for schema files to include.

Allow users to opt out of generated code, component database, etc.

A feature that has repeatedly come up in PR reviews and discussions has been allowing users to replace the default generated code or opt out entirely and pass in raw Schema objects.

Currently, the implementation assumes that you are using generated code of a specific shape and structure. (i.e. - using the Component trait, using inventory for auto-registration).

We should allow and support for this level of granularity and let users opt in at whichever level they want. From high level to low level:

  1. Use default generated code and associated traits, types, etc.
  2. Custom generated code, but still using the associated traits, types, etc.
  3. Opt out of generated code and associated traits, types, etc. entirely.

Using cargo feature flags may be an appropriate way of handling this - but some decisions will need to be made around which direction these feature flags go (i.e. - default is the low level, and you opt into higher level features or default is high level, and you opt out of the high level features).

This will likely be a big chunk of work to isolate out these parts of the codebase and can likely be done post v0.1.0 release

Segfault on master

Running:

 cargo run -- --worker-id RustWorker999 --worker-type RustWorker receptionist

Returns the following:

error: process didn't exit successfully: `C:\Workspace\spatialos-sdk-rs\target\debug\project-example.exe --worker-id RustWorker999 --worker-type RustWorker receptionist` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

I suspect something in either #60 or #73 broke something

Have cargo-spatial generate component-id be aware of existing schema components.

In the Spatial.toml config - we have access to all the schema paths where components can be defined.

This means that we can exclude random draws that collide with existing components.

Big decision is whether to:

  • Parse the schema files manually, through regex or tokenization.
  • Invoke the schema_compiler to generate the bundle.json and read the IDs from there. This temporary json file can be discarded after the JSON is parsed.

At first glance, I lean toward the latter option

Add License

Dual Apache and MIT appears to be the Rust ecosystem standard

Add constructors for generated types.

Currently, the only way to create generated types is to use the object initializer syntax (or at least that's what its called in C#). Instead, we should provide a constructor as well to cut down the verbosity.

Currently we have:

improbable::Coordinates { 
	x: 0.0, 
	y: 0.0, 
	z: 0.0 
}

where we could have:

improbable::Coordinates::new(0.0, 0.0, 0.0)

Add Ergonomic API for interacting with component updates

Constructing component updates is pretty verbose right now:

I.e. -

improbable::PositionUpdate {
                    coords: Some(improbable::Coordinates {
                        x: angle.sin() * 10.0,
                        y: 0.0,
                        z: angle.cos() * 10.0,
                    }),
                },

vs

improbable::PositionUpdate::new()
	.with_coords(improbable::Coordinates { 
		x: angle.sin() * 10.0, 
		y: 0.0, 
		z: angle.cos() * 10.0,
	})

This problem will get worse if you have a component with many fields.

As a sidenote - constructors for generated types would help as well :)

Error Handing User Story

Error handling needs to be fleshed out in the SDK crate - most places use Result<T, String> as the result type - which is fine for debugging purposes, but we likely will want to build a proper error type which users can match on.

Do we use crates like failure, or is that too heavyweight for the size of the project?

Developer workflow for cargo-spatial

Now that cargo-spatial is (about to be) on the master branch, we should start to discuss what sort of workflows should be supported, and what a good default workflow/setup would be.

To start off, the initial functionality I added was the cargo spatial local launch command, which performs the following steps:

  • Runs code generation.
  • Builds all workers.
  • Copies all workers to the build directory.
  • Runs spatial local launch.

The idea here was to automate all the manual steps that I was having to do in order to launch a local deployment to test my workers.

While this workflow has been all I've needed so far for a super simple test game, @jamiebrynes7 has pointed out that his normal workflow is to launch a local deployment and then manually connect workers to it. If I'm understanding it correctly, this is a similar workflow to what the Unity GDK uses, i.e. Ctrl + L launches a local deployment, and then hitting the play button in the Unity editor launches all your workers and connects them to the local deployment.

Would it make sense to support a similar workflow for the Rust SDK? I have very limited experience working with SpatialOS, so I'm not personally familiar with what a "normal" development workflow looks like. What other workflows should we support? Does it even make sense for the Rust SDK to emulate the workflow of the Unity GDK, or should that be left to engine-specific integrations to do?

Connecting with the same worker id twice can segfault or stack overflow.

Oddly this is not repro with 100% accuracy:

Jamie Brynes@Jamie-Desktop /c/Workspace/spatialos-sdk-rs/project-example (feature/complete-snapshot-api)
λ cargo run -- --worker-id RustWorker998 --worker-type RustWorker receptionist
    Finished dev [unoptimized + debuginfo] target(s) in 0.16s
     Running `C:\Workspace\spatialos-sdk-rs\target\debug\project-example.exe --worker-id RustWorker998 --worker-type RustWorker receptionist`
error: process didn't exit successfully: `C:\Workspace\spatialos-sdk-rs\target\debug\project-example.exe --worker-id RustWorker998 --worker-type RustWorker receptionist` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)
Segmentation fault
Jamie Brynes@Jamie-Desktop /c/Workspace/spatialos-sdk-rs/project-example (feature/complete-snapshot-api)
λ cargo run -- --worker-id RustWorker998 --worker-type RustWorker receptionist
    Finished dev [unoptimized + debuginfo] target(s) in 0.16s
     Running `C:\Workspace\spatialos-sdk-rs\target\debug\project-example.exe --worker-id RustWorker998 --worker-type RustWorker receptionist`
memory allocation of 27086784456 bytes failederror: process didn't exit successfully: `C:\Workspace\spatialos-sdk-rs\target\debug\project-example.exe --worker-id RustWorker998 --worker-type RustWorker receptionist` (exit code: 0xc0000409, STATUS_STACK_BUFFER_OVERRUN)
Jamie Brynes@Jamie-Desktop /c/Workspace/spatialos-sdk-rs/project-example (feature/complete-snapshot-api)
λ cargo run -- --worker-id RustWorker998 --worker-type RustWorker receptionist
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
     Running `C:\Workspace\spatialos-sdk-rs\target\debug\project-example.exe --worker-id RustWorker998 --worker-type RustWorker receptionist`
thread 'main' panicked at 'Log in via receptionist failed: gRPC error INVALID_ARGUMENT: Worker ID RustWorker998 has already been used. Please use a different one.', project-example\src\main.rs:30:19
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
error: process didn't exit successfully: `C:\Workspace\spatialos-sdk-rs\target\debug\project-example.exe --worker-id RustWorker998 --worker-type RustWorker receptionist` (exit code: 101)

My guess is some assumption is being broken when this errors.

Add Appveyor support for Windows builds

Due to Travis having issues with Windows machines spinning up and hanging (causing the build to terminate and fail) - this was disabled. Windows is an important platform and should be supported.

Download the SpatialOS SDK as part of cargo-spatial

Users need to have the SDK downloaded before they can compile the Rust SDK, and this is currently handled as a manual step before they can even build their project. We should handle this automatically as part of cargo-spatial. This should include specifying a library version in your Spatial.toml configuration file.

ReserveEntityIdsResponse should return the list of entities as an iterator

Currently (as of my PR which implements send_component_update), ReserveEntityIdsResponse defines a range of entity IDs as: "first entity ID" and "number of entity IDs".

We can probably turn this into some kind of generator pattern, that allows someone to .take() an entity ID from the reserved range.

Add CLI downloading for Linux

Currently CLI downloading is only implemented for Windows and MacOS since there are installers for those OSes.

For Linux we can grab the binary directly, put it somewhere and chmod +x it for the user.

The question is.. how do we ask them for the path?
Do we put it in the local directory?
Do we put it in a .spatial directory?
Do we put it in /usr/bin or similar?

Implement Connection trait for WorkerConnection

Tracking issue for implementing the connection trait.

  • send_log_message
  • send_metrics
  • send_reserve_entity_ids_request
  • send_create_entity_request
  • send_delete_entity_request
  • send_entity_query_request
  • send_command_request
  • send_command_response
  • send_command_failure
  • send_component_update
  • send_component_interest
  • send_authority_loss_imminent_acknowledgement
  • set_protocol_logging_enabled
  • is_connected
  • get_worker_id
  • get_worker_attributes
  • get_worker_flag
  • get_op_list

Implement EntitySnapshot

This should mainly be used in calling CreateEntity, query snapshot result, and the snapshot APIs.

Code generator plugins/extensions

As part of #107, the idea of code generator plugins was brought up. This issue should track this particular use case and possible solutions.

  • Plugins using WASM, or some dynamically loaded library
  • Shipping codegen as a library and have users write a build.rs to configure the plugins.

Schema serialization rewrite and migration

After a couple of fruitful-but-failed attempts at rewriting the schema serialization logic (#89, #101), I've now got a better idea of how to improve the serialization system. At this point, the problem is figuring out how to migrate to the improved system without having to do a single, massive PR. The goal of this ticket is to lay out a plan for that migration, and to act as a tracker for the progress.

The previous pass at rewriting the serialization system is archived in a fork of the repository.

Progress is also tracked in a project.

Rationale

There are a number of problems that I am attempting to solve with this rewrite:

  • Safety issues with schema data objects. The way that the schema data objects are exposed is wildly unsafe. It does not enforce the required ownership for data, exposing soundness holes in safe Rust code.
  • Better encode schema serialization rules using Rust's type system. The current setup doesn't do a good job statically enforcing that schema types match the semantics of the schema language.
  • Provide a more idiomatic API. If possible, we should more closely mirror how serde handles serialization.

Migration Steps

The goal is to break this work up into smaller steps that can each be merged in one at a time in the hopes of making the migration work more manageable.

  • Restructure module hierarchy. This is an easy first step and will help make future refactorings less messy. (#122)
    • Move schema out of internal.
    • Move schema primitive types into submodule of schema.
    • Move schema object types into separate submodules of schema.
  • Remove SchemaFieldContainer. The current setup is incompatible with the safety fixes, so getting this done will help keep the later changes smaller. (#124)
  • Address schema object safety issues. This is relatively self-contained work, and can be done without changing how serialization itself is handled.
    • SchemaObject (#127)
    • SchemaComponentData (#128)
    • SchemaComponentUpdate (#129)
    • SchemaCommandRequest (#129)
    • SchemaCommandResponse (#129)
  • Rework schema serialization setup.
    • Compare the current setup to the ideal setup.
    • Determine more granular migration steps.
    • Get collection types (list, map, and option) and object types represented properly in the serialization traits. This will basically mean updating SchemaFieldPrimitive to be a more generalized SchemaField trait. It will also allow us to drastically simplify code generation, which will made the subsequent steps easier.
    • Split off the IndexedField and ArrayField traits.
    • Update Component to more directly defer to the main serialization traits.
    • Split out separate Update, CommandResponse, and CommandRequest traits.

Download the spatial CLI as part of cargo-spatial

We currently require that the user install the spatial CLI tool separately in order to be able to use cargo-spatial. We should reasonably be able to automate this in cargo-spatial, automatically downloading the CLI and installing it to a local directory if the user doesn't have it installed already.

Automatic component registration

Currently users have to manually build the ComponentDatabase in order to enable automatic serialization. This means that they have to remember to add every single component in the standard schema library, their project, and any types from third-party libraries they use. This process is tedious and error-prone, and would benefit from an automated solution. There are two possible approaches I can think of:

First, have the code generation process generate a helper function that builds a ComponentDatabase with all the component types. Since code generation currently knows about all component types in the project, it should be relatively trivial to do this. However, #56 proposes that we change that, so we'll have to be cognizant of how these two proposals interact.

The other approach is to use the inventory crate to gather up all component types defined in all compiled crates (including third-party crates) and build a ComponentDatabase from them. This may interact better with #56 if we can't build the component database directly as part of code generation.

Brainstorming: Amethyst and Specs + SpatialOS

Amethyst is a game engine for rust which uses Specs as its entity-component-system implementation. Rather than trying to build a GDK, I'm exploring what it would take to integrate specs with the SDK provided by this repository.

As a first step, I'm going to try and accomplish this without any code-generation, just wrapping the spatial-generated components with a newtype struct that implements specs Component trait. Then, I'll implement generic component reader/writer Systems which interact with a mutex-protected WorkerConnection as a world resource.

Assuming that all of that goes well (and I'll share my results) the natural next step would be to auto-generate some implementations. Looks like this has been talked about in #83. Generating the systems isn't a big deal, but due to Rust's restriction that implementations for third-party traits must live alongside type definitions, the implementation of specs::Component for demo::Rotate would have to be part of the spatial codegen process.

At a high level, it would be nice to do something like

cargo spatial codegen --plugin specs

though I'm not sure how the custom generation code would be provided to the spatial cargo component.

How about using a build.rs file instead of a cargo component? That would require (and maybe this is already possible but not "first class") exposing the code generation as a library.

Incorrect code generation for external types

If I have a component schema that references an imported type (i.e. a type from a different namespace than the current file), the generated Rust code uses the incorrect path to refer to corresponding Rust type. For example, for the following component definition:

component Rotate {
    id = 1001;

    double angle = 1;
    double radius = 5;
    improbable.Vector3d center = 6;
}

The following Rust code is generated:

#[derive(Debug, Clone)]
pub struct Rotate {
    pub angle: f64,
    pub radius: f64,
    pub center: generated::example::Vector3d,
}

Note that the center field has the type generated::example::Vector3d, which is incorrect and should instead be generated::improbable::Vector3d.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.