├── ci ├── spatial.cmd ├── spatial ├── Dockerfile └── build.sh ├── .toolsharerc ├── snapshots ├── default.snapshot └── default.json ├── workers ├── External │ ├── .gitignore │ ├── cmake_configure.sh │ ├── spatialos_worker_packages.json │ ├── CMakeLists.txt │ ├── spatialos.External.worker.json │ ├── build.json │ └── src │ │ └── startup.cc └── Managed │ ├── .gitignore │ ├── cmake_configure.sh │ ├── spatialos_worker_packages.json │ ├── CMakeLists.txt │ ├── spatialos.Managed.worker.json │ ├── build.json │ └── src │ └── startup.cc ├── spatialos.json ├── .gitignore ├── schema ├── sample.schema └── CMakeLists.txt ├── .buildkite ├── premerge.definition.yaml └── premerge.steps.yaml ├── default_launch.json ├── dependencies └── CMakeLists.txt ├── .clang-format └── README.md /ci/spatial.cmd: -------------------------------------------------------------------------------- 1 | bash "%~dp0spatial" %* 2 | -------------------------------------------------------------------------------- /.toolsharerc: -------------------------------------------------------------------------------- 1 | pinned_tool: 2 | - tool: "imp-ci" 3 | version: "20200206.175502.ed41e47ac7" 4 | -------------------------------------------------------------------------------- /snapshots/default.snapshot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spatialos/CppBlankProject/HEAD/snapshots/default.snapshot -------------------------------------------------------------------------------- /ci/spatial: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | "${TOOLS_DIR}"/spatial --log_level=debug --oauth2_client_cli_token_directory="${AUTH_DIR}" "$@" 3 | -------------------------------------------------------------------------------- /workers/External/.gitignore: -------------------------------------------------------------------------------- 1 | # This worker is using a custom build.json 2 | !build.json 3 | !spatialos_worker_packages.json 4 | 5 | # CMake 6 | cmake_build/ -------------------------------------------------------------------------------- /workers/Managed/.gitignore: -------------------------------------------------------------------------------- 1 | # This worker is using a custom build.json 2 | !build.json 3 | !spatialos_worker_packages.json 4 | 5 | # CMake 6 | cmake_build/ -------------------------------------------------------------------------------- /spatialos.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "your_project_name_here", 3 | "project_version": "1.0.0", 4 | "sdk_version": "15.0.0", 5 | "dependencies": [ 6 | { 7 | "name": "standard_library", 8 | "version": "15.0.0" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SpatialOS project build directory 2 | build/ 3 | 4 | # Cache and other project metadata 5 | .spatialos/ 6 | 7 | # Logs 8 | spatial.log 9 | logs/ 10 | 11 | # Worker packages and other dependencies (be careful not to ignore the whole dependencies folder) 12 | dependencies/worker_sdk 13 | 14 | # Schema generated sources 15 | generated_code/ 16 | -------------------------------------------------------------------------------- /schema/sample.schema: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | import "improbable/standard_library.schema"; 4 | 5 | component LoginListener { 6 | id = 1000; 7 | } 8 | 9 | component_set LoginListenerSet { 10 | id = 2000; 11 | components = [LoginListener]; 12 | } 13 | 14 | component_set PositionSet { 15 | id = 2001; 16 | components = [improbable.Position]; 17 | } 18 | -------------------------------------------------------------------------------- /ci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y \ 5 | cmake \ 6 | g++ \ 7 | gcc \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | ARG USER_ID 11 | ARG GROUP_ID 12 | 13 | RUN addgroup --gid $GROUP_ID user \ 14 | && adduser --disabled-password --uid $USER_ID --gid $GROUP_ID user 15 | USER user 16 | -------------------------------------------------------------------------------- /workers/Managed/cmake_configure.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -u -o pipefail 4 | 5 | if [[ "$(uname -s)" == "Linux" ]]; then 6 | cmake .. -DCMAKE_C_FLAGS=-m64 -G "Unix Makefiles" 7 | elif [[ "$(uname -s)" == "Darwin" ]]; then 8 | cmake .. -DCMAKE_OSX_ARCHITECTURES=x86_64 -G "Unix Makefiles" 9 | else 10 | cmake .. -DCMAKE_GENERATOR_PLATFORM=x64 11 | fi 12 | -------------------------------------------------------------------------------- /workers/External/cmake_configure.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -u -o pipefail 4 | 5 | if [[ "$(uname -s)" == "Linux" ]]; then 6 | cmake .. -DCMAKE_C_FLAGS=-m64 -G "Unix Makefiles" 7 | elif [[ "$(uname -s)" == "Darwin" ]]; then 8 | cmake .. -DCMAKE_OSX_ARCHITECTURES=x86_64 -G "Unix Makefiles" 9 | else 10 | cmake .. -DCMAKE_GENERATOR_PLATFORM=x64 11 | fi 12 | -------------------------------------------------------------------------------- /.buildkite/premerge.definition.yaml: -------------------------------------------------------------------------------- 1 | agent_queue_id: trigger-pipelines 2 | description: C++ Blank Project premerge pipeline 3 | github: 4 | branch_configuration: [] 5 | default_branch: master 6 | pull_request_branch_filter_configuration: [] 7 | teams: 8 | - name: Everyone 9 | permission: BUILD_AND_READ 10 | - name: gen/team/worker 11 | permission: MANAGE_BUILD_AND_READ 12 | -------------------------------------------------------------------------------- /workers/External/spatialos_worker_packages.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "path": "../../dependencies/worker_sdk/headers", 5 | "type": "worker_sdk", 6 | "packages": [ 7 | { 8 | "name": "cpp_headers" 9 | } 10 | ] 11 | }, 12 | { 13 | "path": "../../dependencies/worker_sdk/lib", 14 | "type": "worker_sdk", 15 | "packages": [ 16 | { 17 | "name": "c-dynamic-x86_64-clang-macos", 18 | "platform": "macos" 19 | }, 20 | { 21 | "name": "c-dynamic-x86_64-clang1000-linux", 22 | "platform": "linux" 23 | }, 24 | { 25 | "name": "c-dynamic-x86_64-vc141_md-win32", 26 | "platform": "windows" 27 | } 28 | ] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /workers/Managed/spatialos_worker_packages.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "path": "../../dependencies/worker_sdk/headers", 5 | "type": "worker_sdk", 6 | "packages": [ 7 | { 8 | "name": "cpp_headers" 9 | } 10 | ] 11 | }, 12 | { 13 | "path": "../../dependencies/worker_sdk/lib", 14 | "type": "worker_sdk", 15 | "packages": [ 16 | { 17 | "name": "c-dynamic-x86_64-clang-macos", 18 | "platform": "macos" 19 | }, 20 | { 21 | "name": "c-dynamic-x86_64-clang1000-linux", 22 | "platform": "linux" 23 | }, 24 | { 25 | "name": "c-dynamic-x86_64-vc141_md-win32", 26 | "platform": "windows" 27 | } 28 | ] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /schema/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This script is included by worker and library builds 2 | # It is not meant to be built as a standalone library 3 | 4 | set(GENERATED_CODE_DIR "${APPLICATION_ROOT}/generated_code/cpp") 5 | 6 | # Schema generated code in library 7 | file(GLOB_RECURSE SCHEMA_FILES 8 | "*.schema" 9 | ) 10 | 11 | file(GLOB_RECURSE SCHEMA_SOURCE_FILES 12 | "${GENERATED_CODE_DIR}/*.cc" 13 | "${GENERATED_CODE_DIR}/*.h" 14 | ) 15 | 16 | source_group(schema "${CMAKE_CURRENT_SOURCE_DIR}/[^/]*") 17 | source_group(generated_code\\schema "${GENERATED_CODE_DIR}/[^/]*") 18 | source_group(generated_code\\improbable "${GENERATED_CODE_DIR}/improbable/[^/]*") 19 | 20 | add_library(Schema STATIC ${SCHEMA_FILES} ${SCHEMA_SOURCE_FILES}) 21 | target_include_directories(Schema SYSTEM PUBLIC "${GENERATED_CODE_DIR}") 22 | 23 | target_link_libraries(Schema PRIVATE WorkerSdk) -------------------------------------------------------------------------------- /default_launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "w2_r0500_e5", 3 | "world": { 4 | "snapshots": { 5 | "snapshot_write_period_seconds": 0 6 | }, 7 | "dimensions": { 8 | "x_meters": 1500, 9 | "z_meters": 1500 10 | } 11 | }, 12 | "load_balancing": { 13 | "layer_configurations": [ 14 | { 15 | "layer": "Managed", 16 | "rectangle_grid": { 17 | "cols": 1, 18 | "rows": 1 19 | } 20 | } 21 | ] 22 | }, 23 | "workers": [ 24 | { 25 | "worker_type": "Managed", 26 | "permissions": [{ 27 | "entity_creation": { 28 | "allow": true 29 | }, 30 | "entity_deletion": { 31 | "allow": true 32 | }, 33 | "entity_query": { 34 | "allow": true, 35 | "components": ["*"] 36 | }, 37 | "system_entity_command": { 38 | "allow": true 39 | } 40 | }] 41 | }, 42 | { 43 | "worker_type": "External", 44 | "permissions": [{ 45 | "entity_creation": { 46 | "allow": false 47 | }, 48 | "entity_deletion": { 49 | "allow": false 50 | }, 51 | "entity_query": { 52 | "allow": true, 53 | "components": ["*"] 54 | } 55 | }] 56 | } 57 | ] 58 | } 59 | -------------------------------------------------------------------------------- /workers/External/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Builds a SpatialOS worker using the C++ API 2 | 3 | # Replace project name below with your own worker name 4 | # !!! This needs to match the artifact_name in spatialos..worker.json 5 | # !!! because ${PROJECT_NAME} is used below when creating the zip target 6 | project(External) 7 | cmake_minimum_required(VERSION 3.7) 8 | 9 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 10 | set(CMAKE_BUILD_RPATH "$ORIGIN") 11 | 12 | set(APPLICATION_ROOT "${PROJECT_SOURCE_DIR}/../..") 13 | set(SCHEMA_SOURCE_DIR "${APPLICATION_ROOT}/schema") 14 | set(WORKER_SDK_DIR "${APPLICATION_ROOT}/dependencies") 15 | 16 | # Strict warnings. 17 | if(MSVC) 18 | add_definitions(/W2) 19 | else() 20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 21 | add_definitions(-Wall -Wextra -Werror -pedantic) 22 | endif() 23 | 24 | add_subdirectory(${WORKER_SDK_DIR} "${CMAKE_CURRENT_BINARY_DIR}/WorkerSdk") 25 | add_subdirectory(${SCHEMA_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/Schema") 26 | 27 | # Set the default Visual Studio startup project to the worker itself. This only has an effect from 28 | # CMake 3.6 onwards. 29 | set(VS_STARTUP_PROJECT ${PROJECT_NAME}) 30 | 31 | # The worker binary. 32 | file(GLOB_RECURSE SOURCE_FILES 33 | "src/*.cc" 34 | "src/*.cpp" 35 | "src/*.h" 36 | "src/*.hpp") 37 | add_executable(${PROJECT_NAME} ${SOURCE_FILES}) 38 | target_link_libraries(${PROJECT_NAME} WorkerSdk Schema) 39 | 40 | # Creates a zip file with the worker executable and the dynamic worker library. 41 | create_worker_zip(${PROJECT_NAME}) 42 | -------------------------------------------------------------------------------- /workers/Managed/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Builds a SpatialOS worker using the C++ API 2 | 3 | # Replace project name below with your own worker name 4 | # !!! This needs to match the artifact_name in spatialos..worker.json 5 | # !!! because ${PROJECT_NAME} is used below when creating the zip target 6 | project(Managed) 7 | cmake_minimum_required(VERSION 3.7) 8 | 9 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 10 | set(CMAKE_BUILD_RPATH "$ORIGIN") 11 | 12 | set(APPLICATION_ROOT "${PROJECT_SOURCE_DIR}/../..") 13 | set(SCHEMA_SOURCE_DIR "${APPLICATION_ROOT}/schema") 14 | set(WORKER_SDK_DIR "${APPLICATION_ROOT}/dependencies") 15 | 16 | # Strict warnings. 17 | if(MSVC) 18 | add_definitions(/W2) 19 | else() 20 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 21 | add_definitions(-Wall -Wextra -Werror -pedantic) 22 | endif() 23 | 24 | add_subdirectory(${WORKER_SDK_DIR} "${CMAKE_CURRENT_BINARY_DIR}/WorkerSdk") 25 | add_subdirectory(${SCHEMA_SOURCE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/Schema") 26 | 27 | # Set the default Visual Studio startup project to the worker itself. This only has an effect from 28 | # CMake 3.6 onwards. 29 | set(VS_STARTUP_PROJECT ${PROJECT_NAME}) 30 | 31 | # The worker binary. 32 | file(GLOB_RECURSE SOURCE_FILES 33 | "src/*.cc" 34 | "src/*.cpp" 35 | "src/*.h" 36 | "src/*.hpp") 37 | add_executable(${PROJECT_NAME} ${SOURCE_FILES}) 38 | target_link_libraries(${PROJECT_NAME} WorkerSdk Schema) 39 | 40 | # Creates a zip file with the worker executable and the dynamic worker library. 41 | create_worker_zip(${PROJECT_NAME}) 42 | -------------------------------------------------------------------------------- /.buildkite/premerge.steps.yaml: -------------------------------------------------------------------------------- 1 | common: &common 2 | timeout_in_minutes: 60 3 | retry: 4 | automatic: 5 | # These are designed to trap and retry failures because agent lost connection. 6 | # Agent exits with -1 in this case. 7 | - exit_status: -1 8 | limit: 3 9 | - exit_status: 255 10 | limit: 3 11 | - exit_status: 128 12 | limit: 3 13 | 14 | linux_agent: &linux_agent 15 | agents: 16 | - "agent_count=1" 17 | - "capable_of_building=worker-sdk" 18 | - "environment=production" 19 | - "permission_set=builder" 20 | - "platform=linux" 21 | - "scaler_version=2" 22 | - "machine_type=half" 23 | - "queue=${CI_LINUX_BUILDER_QUEUE:-v4-20-03-23-110204-bk9814-9f960b76}" 24 | <<: *common 25 | 26 | macos_agent: &macos_agent 27 | agents: 28 | - "queue=${CI_MACOS_BUILDER_QUEUE:-macos_worker-sdk_17b57b101ca8ff76}" 29 | - "environment=production" 30 | <<: *common 31 | 32 | windows_agent: &windows_agent 33 | agents: 34 | - "agent_count=1" 35 | - "capable_of_building=worker-sdk" 36 | - "environment=production" 37 | - "machine_type=half" 38 | - "permission_set=builder" 39 | - "platform=windows" 40 | - "scaler_version=2" 41 | - "queue=${CI_WINDOWS_BUILDER_QUEUE:-v4-20-03-25-183029-bk9912-014014bb}" 42 | <<: *common 43 | 44 | steps: 45 | - label: "build-linux" 46 | command: "ci/build.sh" 47 | <<: *linux_agent 48 | - label: "build-macos" 49 | command: "ci/build.sh" 50 | <<: *macos_agent 51 | - label: "build-windows" 52 | command: "bash -c ci/build.sh" 53 | <<: *windows_agent 54 | 55 | -------------------------------------------------------------------------------- /snapshots/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "__entity_id": "1", 3 | "improbable.AuthorityDelegation": { 4 | "delegations": [ 5 | { 6 | "key": 2000, 7 | "value": 2 8 | }, 9 | { 10 | "key": 2001, 11 | "value": 2 12 | } 13 | ] 14 | }, 15 | "improbable.Interest": { 16 | "component_set_interest": [ 17 | { 18 | "key": 2000, 19 | "value": { 20 | "queries": [ 21 | { 22 | "constraint": { 23 | "and_constraint": [], 24 | "box_constraint": [], 25 | "component_constraint": [60], 26 | "cylinder_constraint": [], 27 | "entity_id_constraint": [], 28 | "or_constraint": [], 29 | "relative_box_constraint": [], 30 | "relative_cylinder_constraint": [], 31 | "relative_sphere_constraint": [], 32 | "self_constraint": [], 33 | "sphere_constraint": [] 34 | }, 35 | "frequency": [], 36 | "full_snapshot_result": [], 37 | "result_component_id": [60], 38 | "result_component_set_id": [] 39 | } 40 | ] 41 | } 42 | } 43 | ] 44 | }, 45 | "improbable.Metadata": { 46 | "entity_type":"ServerTestEntity" 47 | }, 48 | "improbable.Persistence": {}, 49 | "improbable.Position": { 50 | "coords": { 51 | "x": 0.0, 52 | "y": 0.0, 53 | "z": 0.0 54 | } 55 | }, 56 | "sample.LoginListener": {} 57 | } 58 | { 59 | "__entity_id": "2", 60 | "improbable.Metadata": { 61 | "entity_type":"PartitionEntity" 62 | }, 63 | "improbable.Persistence": {}, 64 | "improbable.Position": { 65 | "coords": { 66 | "x": 0.0, 67 | "y": 0.0, 68 | "z": 0.0 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /workers/Managed/spatialos.Managed.worker.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "tasks_filename": "build.json" 4 | }, 5 | "managed": { 6 | "linux": { 7 | "artifact_name": "Managed@Linux.zip", 8 | "command": "./Managed", 9 | "arguments": [ 10 | "receptionist", 11 | "${IMPROBABLE_RECEPTIONIST_HOST}", 12 | "${IMPROBABLE_RECEPTIONIST_PORT}", 13 | "${IMPROBABLE_WORKER_ID}" 14 | ] 15 | }, 16 | "windows": { 17 | "artifact_name": "Managed@Windows.zip", 18 | "command": "./Managed.exe", 19 | "arguments": [ 20 | "receptionist", 21 | "${IMPROBABLE_RECEPTIONIST_HOST}", 22 | "${IMPROBABLE_RECEPTIONIST_PORT}", 23 | "${IMPROBABLE_WORKER_ID}" 24 | ] 25 | }, 26 | "macos": { 27 | "artifact_name": "Managed@Mac.zip", 28 | "command": "./Managed", 29 | "arguments": [ 30 | "receptionist", 31 | "${IMPROBABLE_RECEPTIONIST_HOST}", 32 | "${IMPROBABLE_RECEPTIONIST_PORT}", 33 | "${IMPROBABLE_WORKER_ID}" 34 | ] 35 | } 36 | }, 37 | "external": { 38 | "local": { 39 | "run_type": "EXECUTABLE_ZIP", 40 | "linux": { 41 | "artifact_name": "Managed@Linux.zip", 42 | "command": "./Managed", 43 | "arguments": [ 44 | "receptionist", 45 | "localhost", 46 | "7777" 47 | ] 48 | }, 49 | "windows": { 50 | "artifact_name": "Managed@Windows.zip", 51 | "command": "./Managed.exe", 52 | "arguments": [ 53 | "receptionist", 54 | "localhost", 55 | "7777" 56 | ] 57 | }, 58 | "macos": { 59 | "artifact_name": "Managed@Mac.zip", 60 | "command": "./Managed", 61 | "arguments": [ 62 | "receptionist", 63 | "localhost", 64 | "7777" 65 | ] 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /workers/External/spatialos.External.worker.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "tasks_filename": "build.json" 4 | }, 5 | "external": { 6 | "local": { 7 | "run_type": "EXECUTABLE_ZIP", 8 | "linux": { 9 | "artifact_name": "External@Linux.zip", 10 | "command": "./External", 11 | "arguments": [ 12 | "receptionist", 13 | "localhost", 14 | "7777", 15 | "ExternalLocalLinux" 16 | ] 17 | }, 18 | "windows": { 19 | "artifact_name": "External@Windows.zip", 20 | "command": "./External.exe", 21 | "arguments": [ 22 | "receptionist", 23 | "localhost", 24 | "7777", 25 | "ExternalLocalWindows" 26 | ] 27 | }, 28 | "macos": { 29 | "artifact_name": "External@Mac.zip", 30 | "command": "./External", 31 | "arguments": [ 32 | "receptionist", 33 | "localhost", 34 | "7777", 35 | "ExternalLocalMac" 36 | ] 37 | } 38 | }, 39 | "cloud": { 40 | "run_type": "EXECUTABLE_ZIP", 41 | "linux": { 42 | "artifact_name": "External@Linux.zip", 43 | "command": "./External", 44 | "arguments": [ 45 | "locator", 46 | "locator.improbable.io", 47 | "${IMPROBABLE_PROJECT_NAME}" 48 | ] 49 | }, 50 | "windows": { 51 | "artifact_name": "External@Windows.zip", 52 | "command": "./External.exe", 53 | "arguments": [ 54 | "locator", 55 | "locator.improbable.io", 56 | "${IMPROBABLE_PROJECT_NAME}" 57 | ] 58 | }, 59 | "macos": { 60 | "artifact_name": "External@Mac.zip", 61 | "command": "./External", 62 | "arguments": [ 63 | "locator", 64 | "locator.improbable.io", 65 | "${IMPROBABLE_PROJECT_NAME}" 66 | ] 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /workers/Managed/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "name": "Codegen", 5 | "steps": [ 6 | { 7 | "name": "C++", 8 | "arguments": [ 9 | "process_schema", 10 | "generate", 11 | "--cachePath=../../.spatialos/schema_codegen_cache", 12 | "--output=../../generated_code/cpp", 13 | "--language=cpp" 14 | ] 15 | } 16 | ] 17 | }, 18 | { 19 | "name": "Build", 20 | "steps": [ 21 | { 22 | "name": "Codegen", 23 | "arguments": [ 24 | "invoke-task", 25 | "Codegen" 26 | ] 27 | }, 28 | { 29 | "name": "Install dependencies", 30 | "arguments": [ 31 | "worker_package", 32 | "unpack" 33 | ] 34 | }, 35 | { 36 | "name": "CMake build directory", 37 | "command": "cmake", 38 | "arguments": [ 39 | "-E", 40 | "make_directory", 41 | "cmake_build" 42 | ] 43 | }, 44 | { 45 | "name": "Project files from CMake", 46 | "working_path": "cmake_build", 47 | "command": "bash", 48 | "arguments": [ 49 | "../cmake_configure.sh" 50 | ] 51 | }, 52 | { 53 | "name": "Worker zip", 54 | "working_path": "cmake_build", 55 | "command": "cmake", 56 | "arguments": [ 57 | "--build", ".", 58 | "--config", "Release", 59 | "--target", "ManagedZip" 60 | ] 61 | } 62 | ] 63 | }, 64 | { 65 | "name": "Clean", 66 | "steps": [ 67 | { 68 | "name": "Generated code", 69 | "arguments": [ 70 | "process_schema", 71 | "clean", 72 | "--cachePath=../../.spatialos/schema_codegen_cache", 73 | "../../.spatialos/schema_codegen_proto", 74 | "../../generated_code/cpp" 75 | ] 76 | }, 77 | { 78 | "name": "Dependencies", 79 | "arguments": [ 80 | "worker_package", 81 | "clean" 82 | ] 83 | }, 84 | { 85 | "name": "CMake build", 86 | "command": "cmake", 87 | "arguments": [ 88 | "-E", 89 | "remove_directory", 90 | "cmake_build" 91 | ] 92 | } 93 | ] 94 | } 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /workers/External/build.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "name": "Codegen", 5 | "steps": [ 6 | { 7 | "name": "C++", 8 | "arguments": [ 9 | "process_schema", 10 | "generate", 11 | "--cachePath=../../.spatialos/schema_codegen_cache", 12 | "--output=../../generated_code/cpp", 13 | "--language=cpp" 14 | ] 15 | } 16 | ] 17 | }, 18 | { 19 | "name": "Build", 20 | "steps": [ 21 | { 22 | "name": "Codegen", 23 | "arguments": [ 24 | "invoke-task", 25 | "Codegen" 26 | ] 27 | }, 28 | { 29 | "name": "Install dependencies", 30 | "arguments": [ 31 | "worker_package", 32 | "unpack" 33 | ] 34 | }, 35 | { 36 | "name": "CMake build directory", 37 | "command": "cmake", 38 | "arguments": [ 39 | "-E", 40 | "make_directory", 41 | "cmake_build" 42 | ] 43 | }, 44 | { 45 | "name": "Project files from CMake", 46 | "working_path": "cmake_build", 47 | "command": "bash", 48 | "arguments": [ 49 | "../cmake_configure.sh" 50 | ] 51 | }, 52 | { 53 | "name": "Worker zip", 54 | "working_path": "cmake_build", 55 | "command": "cmake", 56 | "arguments": [ 57 | "--build", ".", 58 | "--config", "Release", 59 | "--target", "ExternalZip" 60 | ] 61 | } 62 | ] 63 | }, 64 | { 65 | "name": "Clean", 66 | "steps": [ 67 | { 68 | "name": "Generated code", 69 | "arguments": [ 70 | "process_schema", 71 | "clean", 72 | "--cachePath=../../.spatialos/schema_codegen_cache", 73 | "../../.spatialos/schema_codegen_proto", 74 | "../../generated_code/cpp" 75 | ] 76 | }, 77 | { 78 | "name": "Dependencies", 79 | "arguments": [ 80 | "worker_package", 81 | "clean" 82 | ] 83 | }, 84 | { 85 | "name": "CMake build", 86 | "command": "cmake", 87 | "arguments": [ 88 | "-E", 89 | "remove_directory", 90 | "cmake_build" 91 | ] 92 | } 93 | ] 94 | } 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /dependencies/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This script is included by worker and library builds 2 | # It is not meant to be built as a standalone library 3 | 4 | # This is defined in spatialos_worker_packages.json (usually in the directory of each project 5 | # that requires the SDK) 6 | set(CPP_WORKER_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/worker_sdk") 7 | 8 | set(CPP_WORKER_SDK_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/worker_sdk/headers/include") 9 | 10 | # Depends on threading libraries. 11 | find_package(Threads) 12 | 13 | # On Windows this finds the import library (.lib) file, which contains information on how to link the .dll. 14 | find_library(LIB_IMPROBABLE_WORKER 15 | NAMES improbable_worker 16 | PATHS "${CPP_WORKER_SDK_DIR}/lib" 17 | NO_DEFAULT_PATH) 18 | 19 | find_file(DLL_IMPROBABLE_WORKER 20 | NAMES improbable_worker.dll 21 | PATHS "${CPP_WORKER_SDK_DIR}/lib" 22 | NO_DEFAULT_PATH) 23 | 24 | # Worker SDK library. 25 | add_library(WorkerSdk INTERFACE) 26 | 27 | target_include_directories(WorkerSdk SYSTEM INTERFACE "${CPP_WORKER_SDK_INCLUDE_DIR}") 28 | 29 | target_link_libraries(WorkerSdk INTERFACE 30 | ${LIB_IMPROBABLE_WORKER} 31 | # Must be at end! 32 | ${CMAKE_THREAD_LIBS_INIT} 33 | ${CMAKE_DL_LIBS}) 34 | 35 | function(CREATE_WORKER_ZIP WORKER) 36 | # Set artifact subdirectories. 37 | set(WORKER_PACKAGE_COMMAND "spatial") 38 | # WORKER_BUILD_DIR should not be changed so that spatial local launch 39 | # and spatial upload can find the worker assemblies 40 | set(WORKER_BUILD_DIR "${APPLICATION_ROOT}/build/assembly/worker") 41 | 42 | if (MSVC) 43 | set(WORKER_RUNTIME_LIBRARY ${DLL_IMPROBABLE_WORKER}) 44 | else() 45 | SET(WORKER_RUNTIME_LIBRARY ${LIB_IMPROBABLE_WORKER}) 46 | endif() 47 | 48 | # Place the dynamic worker library next to the worker executable. 49 | add_custom_command(TARGET ${WORKER} POST_BUILD 50 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 51 | ${WORKER_RUNTIME_LIBRARY} 52 | $) 53 | 54 | # File name of the dynamic worker library. 55 | get_filename_component(WORKER_RUNTIME_LIBRARY_FILE_NAME ${WORKER_RUNTIME_LIBRARY} NAME) 56 | 57 | # Zip the executable and dynamic worker library. 58 | add_custom_target( 59 | ${WORKER}Zip ALL 60 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 61 | COMMAND ${CMAKE_COMMAND} -E make_directory "${WORKER_BUILD_DIR}" 62 | COMMAND ${WORKER_PACKAGE_COMMAND} file zip -b "$" 63 | -o "${WORKER_BUILD_DIR}/${WORKER}" 64 | --worker_platform=current 65 | "$" 66 | ${WORKER_RUNTIME_LIBRARY_FILE_NAME} 67 | DEPENDS ${WORKER}) 68 | endfunction() 69 | -------------------------------------------------------------------------------- /ci/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -u -x -o pipefail 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | if [[ -z ${BUILDKITE+x} ]]; then 8 | echo "This script is intended to run in CI only." 9 | exit 1 10 | fi 11 | 12 | SPATIALOS_TOOLBELT_VERSION="latest" 13 | 14 | TOOLBELT_PLATFORM="" 15 | SPATIAL_PLATFORM="" 16 | SPATIAL_BINARY="" 17 | 18 | # Setup platform specific environment variables. 19 | if [[ "$(uname -s)" == "Linux" ]]; then 20 | TOOLBELT_PLATFORM="linux" 21 | SPATIAL_PLATFORM="linux" 22 | SPATIAL_BINARY="spatial" 23 | elif [[ "$(uname -s)" == "Darwin" ]]; then 24 | TOOLBELT_PLATFORM="mac" 25 | SPATIAL_PLATFORM="macos" 26 | SPATIAL_BINARY="spatial" 27 | else 28 | TOOLBELT_PLATFORM="win" 29 | SPATIAL_PLATFORM="windows" 30 | SPATIAL_BINARY="spatial.exe" 31 | fi 32 | 33 | # Create temporary directories for the spatial binary and authentication token. 34 | export AUTH_DIR="$(mktemp -d)" 35 | export TOOLS_DIR="$(mktemp -d)" 36 | 37 | # Retrieve the authentication token. 38 | if [[ "$SPATIAL_PLATFORM" == "macos" ]]; then 39 | imp-vault read-key \ 40 | --environment=production \ 41 | --field=token \ 42 | --key=secret/sync.v1/dev-workflow/production-buildkite/buildkite-agents/spatialos-service-account/ci/improbable/worker-blank-projects-ci-token \ 43 | --vault_role=continuous-integration-production-improbable-iam \ 44 | --write_to="${AUTH_DIR}/oauth2_refresh_token" 45 | else 46 | imp-ci secrets read \ 47 | --environment=production \ 48 | --buildkite-org=improbable \ 49 | --secret-type=spatialos-service-account \ 50 | --secret-name=worker-blank-projects-ci-token \ 51 | --field=token \ 52 | --write-to="${AUTH_DIR}/oauth2_refresh_token" 53 | fi 54 | 55 | # Retrieve the spatial binary. 56 | curl -Ls -o "${TOOLS_DIR}/${SPATIAL_BINARY}.tmp" "https://console.improbable.io/toolbelt/download/${SPATIALOS_TOOLBELT_VERSION}/${TOOLBELT_PLATFORM}" || exit 1 57 | chmod +x "${TOOLS_DIR}/${SPATIAL_BINARY}.tmp" 58 | mv "${TOOLS_DIR}/${SPATIAL_BINARY}.tmp" "${TOOLS_DIR}/${SPATIAL_BINARY}" 59 | 60 | # Build 61 | if [[ "$SPATIAL_PLATFORM" == "linux" ]]; then 62 | docker build \ 63 | -t cpp_exampe_project_image \ 64 | -f ci/Dockerfile \ 65 | --build-arg USER_ID=$(id -u) \ 66 | --build-arg GROUP_ID=$(id -g) \ 67 | . 68 | docker run \ 69 | --rm \ 70 | --volume "${TOOLS_DIR}":/build/tools \ 71 | --env TOOLS_DIR=/build/tools \ 72 | --volume "${AUTH_DIR}":/build/auth \ 73 | --env AUTH_DIR=/build/auth \ 74 | --volume $(pwd):/code \ 75 | --workdir /code \ 76 | cpp_exampe_project_image \ 77 | /bin/bash -c 'export PATH="/code/ci:${PATH}"; \ 78 | spatial build --target '"$SPATIAL_PLATFORM" 79 | else 80 | # Add the spatial wrapper script to the path. 81 | export PATH="$(pwd)/ci":"$PATH" 82 | spatial build --target $SPATIAL_PLATFORM 83 | fi 84 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Left 9 | AlignOperands: false 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: true 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: true 44 | BreakConstructorInitializers: BeforeComma 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 100 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 50 | ConstructorInitializerIndentWidth: 0 51 | ContinuationIndentWidth: 4 52 | Cpp11BracedListStyle: true 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | ExperimentalAutoDetectBinPacking: false 56 | FixNamespaceComments: true 57 | ForEachMacros: 58 | - foreach 59 | - Q_FOREACH 60 | - BOOST_FOREACH 61 | IncludeBlocks: Preserve 62 | IncludeCategories: 63 | - Regex: '".*"' 64 | Priority: 1 65 | - Regex: '^' 68 | Priority: 3 69 | - Regex: '^<.*>' 70 | Priority: 4 71 | IncludeIsMainRegex: '([-_](test|unittest))?$' 72 | IndentCaseLabels: false 73 | IndentPPDirectives: None 74 | IndentWidth: 2 75 | IndentWrappedFunctionNames: false 76 | KeepEmptyLinesAtTheStartOfBlocks: false 77 | MacroBlockBegin: '' 78 | MacroBlockEnd: '' 79 | MaxEmptyLinesToKeep: 1 80 | NamespaceIndentation: None 81 | PenaltyBreakAssignment: 1 82 | PenaltyBreakBeforeFirstCallParameter: 10 83 | PenaltyBreakComment: 400 84 | PenaltyBreakFirstLessLess: 200 85 | PenaltyBreakString: 1000 86 | PenaltyExcessCharacter: 1000000 87 | PenaltyReturnTypeOnItsOwnLine: 50 88 | PointerAlignment: Left 89 | RawStringFormats: 90 | - Language: TextProto 91 | Delimiters: 92 | - 'pb' 93 | BasedOnStyle: google 94 | ReflowComments: true 95 | SortIncludes: true 96 | SortUsingDeclarations: true 97 | SpaceAfterCStyleCast: false 98 | SpaceAfterTemplateKeyword: true 99 | SpaceBeforeAssignmentOperators: true 100 | SpaceBeforeParens: ControlStatements 101 | SpaceInEmptyParentheses: false 102 | SpacesBeforeTrailingComments: 2 103 | SpacesInAngles: false 104 | SpacesInContainerLiterals: true 105 | SpacesInCStyleCastParentheses: false 106 | SpacesInParentheses: false 107 | SpacesInSquareBrackets: false 108 | Standard: c++14 109 | TabWidth: 2 110 | UseTab: Never 111 | --- 112 | Language: Proto 113 | BasedOnStyle: Google 114 | ColumnLimit: 100 115 | --- 116 | Language: Java 117 | BasedOnStyle: Google 118 | AlignAfterOpenBracket: Align 119 | AlignConsecutiveAssignments: false 120 | AlignConsecutiveDeclarations: false 121 | AlignEscapedNewlines: Left 122 | AlignOperands: false 123 | AlignTrailingComments: true 124 | AllowAllParametersOfDeclarationOnNextLine: true 125 | AllowShortBlocksOnASingleLine: false 126 | AllowShortCaseLabelsOnASingleLine: false 127 | AllowShortFunctionsOnASingleLine: None 128 | AllowShortIfStatementsOnASingleLine: false 129 | AllowShortLoopsOnASingleLine: false 130 | AlwaysBreakAfterDefinitionReturnType: None 131 | AlwaysBreakAfterReturnType: None 132 | AlwaysBreakBeforeMultilineStrings: true 133 | BinPackArguments: true 134 | BinPackParameters: true 135 | BraceWrapping: 136 | AfterClass: false 137 | AfterControlStatement: false 138 | AfterEnum: false 139 | AfterFunction: false 140 | AfterExternBlock: false 141 | BeforeCatch: false 142 | BeforeElse: false 143 | IndentBraces: false 144 | SplitEmptyFunction: true 145 | SplitEmptyRecord: true 146 | SplitEmptyNamespace: true 147 | BreakBeforeBinaryOperators: None 148 | BreakBeforeBraces: Attach 149 | BreakBeforeInheritanceComma: false 150 | BreakBeforeTernaryOperators: true 151 | BreakAfterJavaFieldAnnotations: true 152 | BreakStringLiterals: true 153 | ColumnLimit: 120 154 | ContinuationIndentWidth: 4 155 | ExperimentalAutoDetectBinPacking: false 156 | ... 157 | -------------------------------------------------------------------------------- /workers/External/src/startup.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Use this to make a worker::ComponentRegistry. This worker doesn't use any components yet 9 | // For example use worker::Components to track these 10 | // common components 11 | using EmptyRegistry = worker::Components<>; 12 | 13 | // Constants and parameters 14 | const int ErrorExitStatus = 1; 15 | const std::string kLoggerName = "startup.cc"; 16 | const std::uint32_t kGetOpListTimeoutInMilliseconds = 100; 17 | 18 | // Connection helpers 19 | worker::Connection ConnectWithLocator(const std::string hostname, const std::string login_token, 20 | const worker::ConnectionParameters& connection_parameters) { 21 | worker::LogsinkParameters logsink_params; 22 | logsink_params.Type = worker::LogsinkType::kStdout; 23 | logsink_params.FilterParameters.CustomFilter = [](worker::LogCategory categories, 24 | worker::LogLevel level) -> bool { 25 | return level >= worker::LogLevel::kWarn || 26 | (level >= worker::LogLevel::kInfo && categories & worker::LogCategory::kLogin); 27 | }; 28 | worker::LocatorParameters locator_parameters; 29 | locator_parameters.Logsinks.emplace_back(logsink_params); 30 | locator_parameters.PlayerIdentity.LoginToken = login_token; 31 | locator_parameters.UseInsecureConnection = true; 32 | 33 | worker::Locator locator{hostname, locator_parameters}; 34 | 35 | auto future = locator.ConnectAsync(EmptyRegistry{}, connection_parameters); 36 | return future.Get(); 37 | } 38 | 39 | worker::Connection 40 | ConnectWithReceptionist(const std::string hostname, const std::uint16_t port, 41 | const std::string& worker_id, 42 | const worker::ConnectionParameters& connection_parameters) { 43 | auto future = worker::Connection::ConnectAsync(EmptyRegistry{}, hostname, port, worker_id, 44 | connection_parameters); 45 | return future.Get(); 46 | } 47 | 48 | std::string get_random_characters(size_t count) { 49 | const auto randchar = []() -> char { 50 | const char charset[] = 51 | "0123456789" 52 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 53 | "abcdefghijklmnopqrstuvwxyz"; 54 | const auto max_index = sizeof(charset) - 1; 55 | return charset[std::rand() % max_index]; 56 | }; 57 | std::string str(count, 0); 58 | std::generate_n(str.begin(), count, randchar); 59 | return str; 60 | } 61 | 62 | // Entry point 63 | int main(int argc, char** argv) { 64 | auto now = std::chrono::high_resolution_clock::now(); 65 | std::srand( 66 | std::chrono::time_point_cast(now).time_since_epoch().count()); 67 | 68 | auto print_usage = [&]() { 69 | std::cout << "Usage: External receptionist " << std::endl; 70 | std::cout << " External locator "; 71 | std::cout << std::endl; 72 | std::cout << "Connects to SpatialOS" << std::endl; 73 | std::cout << " - hostname of the receptionist or locator to connect to."; 74 | std::cout << std::endl; 75 | std::cout << " - port to use if connecting through the receptionist."; 76 | std::cout << std::endl; 77 | std::cout << " - name of the worker assigned by SpatialOS." << std::endl; 78 | std::cout << " - token to use when connecting through the locator."; 79 | std::cout << std::endl; 80 | }; 81 | 82 | worker::ConnectionParameters parameters; 83 | parameters.WorkerType = "External"; 84 | parameters.Network.ConnectionType = worker::NetworkConnectionType::kKcp; 85 | parameters.Network.Kcp.SecurityType = worker::NetworkSecurityType::kInsecure; 86 | parameters.Network.UseExternalIp = true; 87 | 88 | worker::LogsinkParameters logsink_params; 89 | logsink_params.Type = worker::LogsinkType::kStdout; 90 | logsink_params.FilterParameters.CustomFilter = [](worker::LogCategory categories, 91 | worker::LogLevel level) -> bool { 92 | return level >= worker::LogLevel::kWarn || 93 | (level >= worker::LogLevel::kInfo && categories & worker::LogCategory::kLogin); 94 | }; 95 | parameters.Logsinks.emplace_back(logsink_params); 96 | parameters.EnableLoggingAtStartup = true; 97 | 98 | std::vector arguments; 99 | 100 | // if no arguments are supplied, use the defaults for a local deployment 101 | if (argc == 1) { 102 | arguments = {"receptionist", "localhost", "7777", 103 | parameters.WorkerType + "_" + get_random_characters(4)}; 104 | } else { 105 | arguments = std::vector(argv + 1, argv + argc); 106 | } 107 | 108 | const std::string connection_type = arguments[0]; 109 | if (connection_type != "receptionist" && connection_type != "locator") { 110 | print_usage(); 111 | return ErrorExitStatus; 112 | } 113 | 114 | const bool use_locator = connection_type == "locator"; 115 | 116 | if ((use_locator && arguments.size() != 5) || (!use_locator && arguments.size() != 4)) { 117 | print_usage(); 118 | return ErrorExitStatus; 119 | } 120 | 121 | // Connect with locator or receptionist 122 | worker::Connection connection = use_locator 123 | ? ConnectWithLocator(arguments[1], arguments[2], parameters) 124 | : ConnectWithReceptionist(arguments[1], atoi(arguments[2].c_str()), arguments[3], parameters); 125 | 126 | if (connection.GetConnectionStatusCode() != worker::ConnectionStatusCode::kSuccess) { 127 | std::cerr << "Worker connection failed: " << connection.GetConnectionStatusDetailString() 128 | << std::endl; 129 | return 1; 130 | } 131 | 132 | connection.SendLogMessage(worker::LogLevel::kInfo, kLoggerName, "Connected successfully"); 133 | 134 | // Register callbacks and run the worker main loop. 135 | worker::Dispatcher dispatcher{EmptyRegistry{}}; 136 | 137 | bool is_connected = true; 138 | dispatcher.OnDisconnect([&](const worker::DisconnectOp& op) { 139 | std::cerr << "[disconnect] " << op.Reason << std::endl; 140 | is_connected = false; 141 | }); 142 | 143 | while (is_connected) { 144 | dispatcher.Process(connection.GetOpList(kGetOpListTimeoutInMilliseconds)); 145 | } 146 | 147 | return ErrorExitStatus; 148 | } 149 | -------------------------------------------------------------------------------- /workers/Managed/src/startup.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | // This keeps track of all components and component sets that this worker uses. 16 | // Used to make a worker::ComponentRegistry. 17 | using ComponentRegistry = 18 | worker::Schema; 20 | 21 | // Constants and parameters 22 | const int ErrorExitStatus = 1; 23 | const std::string kLoggerName = "startup.cc"; 24 | const std::uint32_t kGetOpListTimeoutInMilliseconds = 100; 25 | 26 | const worker::EntityId listenerEntity = 1; 27 | const worker::EntityId serverPartitionId = 2; 28 | 29 | worker::Connection 30 | ConnectWithReceptionist(const std::string hostname, const std::uint16_t port, 31 | const std::string& worker_id, 32 | const worker::ConnectionParameters& connection_parameters) { 33 | auto future = worker::Connection::ConnectAsync(ComponentRegistry{}, hostname, port, worker_id, 34 | connection_parameters); 35 | return future.Get(); 36 | } 37 | 38 | std::string get_random_characters(size_t count) { 39 | const auto randchar = []() -> char { 40 | const char charset[] = 41 | "0123456789" 42 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 43 | "abcdefghijklmnopqrstuvwxyz"; 44 | const auto max_index = sizeof(charset) - 1; 45 | return charset[std::rand() % max_index]; 46 | }; 47 | std::string str(count, 0); 48 | std::generate_n(str.begin(), count, randchar); 49 | return str; 50 | } 51 | 52 | // Entry point 53 | int main(int argc, char** argv) { 54 | auto now = std::chrono::high_resolution_clock::now(); 55 | std::srand( 56 | std::chrono::time_point_cast(now).time_since_epoch().count()); 57 | 58 | std::cout << "[local] Worker started " << std::endl; 59 | 60 | auto print_usage = [&]() { 61 | std::cout << "Usage: Managed receptionist " << std::endl; 62 | std::cout << std::endl; 63 | std::cout << "Connects to SpatialOS" << std::endl; 64 | std::cout << " - hostname of the receptionist or locator to connect to."; 65 | std::cout << std::endl; 66 | std::cout << " - port to use if connecting through the receptionist."; 67 | std::cout << std::endl; 68 | std::cout << " - (optional) name of the worker assigned by SpatialOS." 69 | << std::endl; 70 | std::cout << std::endl; 71 | }; 72 | 73 | std::vector arguments; 74 | 75 | // if no arguments are supplied, use the defaults for a local deployment 76 | if (argc == 1) { 77 | arguments = {"receptionist", "localhost", "7777"}; 78 | } else { 79 | arguments = std::vector(argv + 1, argv + argc); 80 | } 81 | 82 | if (arguments.size() != 4 && arguments.size() != 3) { 83 | print_usage(); 84 | return ErrorExitStatus; 85 | } 86 | 87 | worker::ConnectionParameters parameters; 88 | parameters.WorkerType = "Managed"; 89 | parameters.Network.ConnectionType = worker::NetworkConnectionType::kTcp; 90 | parameters.Network.Tcp.SecurityType = worker::NetworkSecurityType::kInsecure; 91 | parameters.Network.UseExternalIp = false; 92 | 93 | worker::LogsinkParameters logsink_params; 94 | logsink_params.Type = worker::LogsinkType::kStdout; 95 | logsink_params.FilterParameters.CustomFilter = [](worker::LogCategory categories, 96 | worker::LogLevel level) -> bool { 97 | return level >= worker::LogLevel::kWarn || 98 | (level >= worker::LogLevel::kInfo && categories & worker::LogCategory::kLogin); 99 | }; 100 | parameters.Logsinks.emplace_back(logsink_params); 101 | parameters.EnableLoggingAtStartup = true; 102 | 103 | std::string workerId; 104 | 105 | // When running as an external worker using 'spatial local worker launch' 106 | // The WorkerId isn't passed, so we generate a random one 107 | if (arguments.size() == 4) { 108 | workerId = arguments[3]; 109 | } else { 110 | workerId = parameters.WorkerType + "_" + get_random_characters(4); 111 | } 112 | 113 | std::cout << "[local] Connecting to SpatialOS as " << workerId << "..." << std::endl; 114 | 115 | // Connect with receptionist 116 | worker::Connection connection = 117 | ConnectWithReceptionist(arguments[1], atoi(arguments[2].c_str()), workerId, parameters); 118 | 119 | if (connection.GetConnectionStatusCode() != worker::ConnectionStatusCode::kSuccess) { 120 | std::cerr << "Worker connection failed: " << connection.GetConnectionStatusDetailString() 121 | << std::endl; 122 | return 1; 123 | } 124 | 125 | connection.SendLogMessage(worker::LogLevel::kInfo, kLoggerName, "Connected successfully"); 126 | 127 | worker::View view{ComponentRegistry{}}; 128 | 129 | bool is_connected = true; 130 | view.OnDisconnect([&](const worker::DisconnectOp& op) { 131 | std::cerr << "[disconnect] " << op.Reason << std::endl; 132 | is_connected = false; 133 | }); 134 | 135 | using AssignPartitionCommand = improbable::restricted::Worker::Commands::AssignPartition; 136 | 137 | // In real code, we would probably want to retry here. 138 | view.OnCommandResponse( 139 | [&](const worker::CommandResponseOp& op) { 140 | if (op.StatusCode == worker::StatusCode::kSuccess) { 141 | connection.SendLogMessage(worker::LogLevel::kInfo, "Server", 142 | "Successfully assigned partition."); 143 | } else { 144 | connection.SendLogMessage(worker::LogLevel::kError, "Server", 145 | "Failed to assign partition: error code : " + 146 | std::to_string(static_cast(op.StatusCode)) + 147 | " message: " + op.Message); 148 | } 149 | }); 150 | 151 | connection.SendCommandRequest( 152 | connection.GetWorkerEntityId(), {serverPartitionId}, /* default timeout */ {}); 153 | 154 | view.OnAddComponent( 155 | [&](worker::AddComponentOp op) { 156 | connection.SendLogMessage(worker::LogLevel::kInfo, "Server", 157 | "Worker with ID " + op.Data.worker_id() + " connected."); 158 | }); 159 | 160 | double elapsed_time = 0.0; 161 | auto last_tick_time = std::chrono::steady_clock::now(); 162 | 163 | while (is_connected) { 164 | view.Process(connection.GetOpList(kGetOpListTimeoutInMilliseconds)); 165 | 166 | if (view.GetAuthority(listenerEntity) == 167 | worker::Authority::kAuthoritative) { 168 | improbable::Position::Update pos_update; 169 | pos_update.set_coords({std::sin(elapsed_time), 0.0, std::cos(elapsed_time)}); 170 | connection.SendComponentUpdate(listenerEntity, pos_update); 171 | } 172 | 173 | auto now = std::chrono::steady_clock::now(); 174 | elapsed_time += std::chrono::duration(now - last_tick_time) 175 | .count(); // Amount of time since last tick, in seconds 176 | last_tick_time = now; 177 | } 178 | 179 | return ErrorExitStatus; 180 | } 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpatialOS C++ Blank project 2 | 3 | This is a SpatialOS project which can serve as a template for building 4 | SpatialOS workers using the C++ SDK. It uses CMake as the build system. If 5 | you're new to SpatialOS, have a look at [Introduction to the C++ worker SDK](https://docs.improbable.io/reference/latest/cppsdk/introduction). 6 | 7 | ## About managed and external workers 8 | 9 | Have a look at the [Glossary entry for 10 | Workers](https://docs.improbable.io/reference/latest/shared/glossary#worker) for a complete discussion and examples. 11 | 12 | ## Dependencies 13 | 14 | This project requires the following software to be installed: 15 | - [CMake](https://cmake.org/) 3.7+ 16 | - _(Windows)_ Visual Studio 2017 or above. 17 | - _(macOS)_ Xcode 18 | - _(Linux)_ `gcc` / `clang` and `make` (for the "Unix Makefiles" CMake project generator) 19 | 20 | ## Quick start 21 | 22 | The build scripts (`build.json`) for each worker: 23 | 24 | 1. Generate C++ code from schema 25 | 2. Download the C++ worker SDK 26 | 3. Create a `cmake_build` directory in the worker directory 27 | 4. Invoke `cmake` with arguments depending on the target platform 28 | 29 | To build and launch a local deployment execute the following commands: 30 | 31 | ``` 32 | spatial worker build 33 | spatial local launch --runtime_version=15.0.0 34 | ``` 35 | 36 | > **Windows users** 37 | > 38 | > If you have [WSL](https://docs.microsoft.com/en-us/windows/wsl/about) installed, it is strongly recommended that you invoke `spatial worker build` directly from a non-WSL bash shell, e.g. git bash. The command executes shell scripts internally, which are likely to fail when invoked from other types of shells (e.g. cmder, powershell). 39 | > Try this if you see the `$'\r': command not found` error, or the `cmake` command not getting found despite being installed. 40 | 41 | To connect a new instance of the "External" worker type to a running local deployment (after `spatial local launch`): 42 | 43 | ``` 44 | spatial local worker launch External local 45 | ``` 46 | 47 | ## What the project does 48 | 49 | When you launch a deployment, a single instance of the `Managed` worker will be started as configured in `default_launch.json`. 50 | 51 | The `Managed` worker connects to SpatialOS and then assigns the entity with ID 2 (defined in the snapshot) as a partition entity to itself. 52 | This then gives it authority over the `LoginListenerSet` and `PositionSet` on entity 1, as per the authority delegation already set up on it in the snapshot. 53 | 54 | The `Managed` worker updates the position of entity 1 in a loop, making it move around the origin in a circle - this will be visible from the inspector. 55 | 56 | The `Interest` component on entity 1 is configured such that the `Managed` worker gains interest in all other entity with the `improbable::restricted::Worker` component. 57 | The `Managed` worker uses this detect when new workers connect to the deployment and log a message to the runtime for each connected worker. 58 | 59 | The `External` worker, once manually started, simply connects to SpatialOS and then loops to continually process the Ops list it receives. 60 | 61 | ## Project structure 62 | 63 | The CMake project hierarchy doesn't exactly match the directory structure of 64 | the project. For example the projects for workers add as subdirectories the 65 | `schema` and `dependencies` projects. 66 | 67 | This is how projects are structured in the directory: 68 | ``` 69 | +-- schema/CMakeLists.txt 70 | +-- dependencies/CMakeLists.txt 71 | +-- workers 72 | |-- External/ 73 | | |-- External/CMakeLists.txt 74 | | |-- CMakeLists.txt 75 | | |-- build.json 76 | |-- Managed/ 77 | |-- Managed/CMakeLists.txt 78 | |-- CMakeLists.txt 79 | |-- build.json 80 | ``` 81 | 82 | This enables you to keep the worker directories free of CMake files for schema and dependencies while not needing a CMake file at the root of the project. 83 | 84 | The `schema` directory contains a sample empty component called `blank`. It is 85 | not used by the workers directly so feel free to delete it but it's there to 86 | show how sources generated from the schema could be linked in the worker 87 | binary. See `schema/CMakeLists.txt` which creates a library with all generated 88 | sources. 89 | 90 | The snapshot exists in both JSON and binary format in the `snapshots` folder. There is no script 91 | to generate the snapshot as the snapshot was written by hand in JSON format, but it's possible 92 | to make simple changes to the JSON snapshot and regenerate the binary snapshot from it. To update the 93 | binary snapshot after making a change, run the following command: 94 | 95 | ``` 96 | spatial project history snapshot convert --input-format=text --input=snapshots/default.json --output-format=binary --output=snapshots/default.snapshot 97 | ``` 98 | 99 | ### The worker project 100 | 101 | The following applies to both the `Managed` and `External` worker projects but examples will only be about `Managed`. 102 | 103 | After running `spatial build` the generated project by CMake will be in 104 | `workers/Managed/cmake_build`. Exactly what it contains will depend on the 105 | generator you use. A worker project includes 3 subdirectories in its 106 | `CMakeLists.txt` - `dependencies`, `schema` and `Managed`. The first two are 107 | not true subdirectories of `workers/Managed` in the file structure but their 108 | binary directories are set as if they were. 109 | 110 | On Windows, both the release and debug builds of the Worker SDK are downloaded and set up correctly in 111 | the `CMakeLists.txt`. This means that both the `Release` and `Debug` configurations in the generated 112 | Visual Studio solution (`.sln`) should build and link correctly without any further changes. 113 | 114 | ## Attaching a debugger 115 | 116 | If you use a Visual Studio generator with CMake, the generated solution contains several projects to match the build targets. You can start a worker from Visual Studio by setting the project matching the worker name as the startup project for the solution. It will try to connect to a local deployment by default. You can customize the connection parameters by navigating to `Properties > Configuration properties > Debugging` to set the command arguments. Using `receptionist localhost 7777 DebugWorker` as the command arguments for example will connect a new instance of the worker named `DebugWorker` via the receptionist to a local running deployment. You can do this for both worker types that come with this project. Make sure you are starting the project using a local debugger (e.g. Local Windows Debugger). 117 | 118 | ## Cloud deployment 119 | 120 | Our cloud deployment environment is based on Linux, so therefore if you're not using Linux, you'll 121 | have to set up a cross-compile toolchain and build out a Linux binary (due to the nature of C++). 122 | More information can be found [here](https://docs.improbable.io/reference/latest/cppsdk/building#building-for-a-cloud-deployment). 123 | 124 | If using Windows, options include (but are not limited to): 125 | - Install [Ubuntu](https://www.microsoft.com/en-gb/p/ubuntu/9nblggh4msv6) using the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10), then build a Linux worker from within that environment. 126 | - Install Ubuntu in a Virtual Machine with either [VirtualBox](https://www.virtualbox.org/wiki/Downloads) or [VMware Workstation Player](https://www.vmware.com/products/workstation-player/workstation-player-evaluation.html). 127 | - Set up a cross-compiler to be used from Windows, such as the one distributed by Unreal Engine: https://docs.unrealengine.com/en-us/Platforms/Linux/GettingStarted 128 | - Use the Visual Studio 2017 CMake Linux support: https://docs.microsoft.com/en-us/cpp/linux/cmake-linux-project?view=vs-2017 129 | 130 | Once this is done and you have successfully built a Linux assembly, set the `project_name` field in 131 | `spatialos.json` to match your SpatialOS project name. Then upload and launch: 132 | 133 | ``` 134 | spatial cloud upload 135 | spatial cloud launch default_launch.json --snapshot= --runtime_version=15.0.0 136 | ``` 137 | 138 | See [`spatial cloud connect external`](https://docs.improbable.io/reference/latest/shared/spatial-cli/spatial-cloud-connect-external) 139 | if you want to connect to a cloud deployment. In 140 | addition, the `External` worker has a second external launch configuration 141 | called `cloud` which could be used to connect provided that you know the 142 | deployment name and have a login token: 143 | 144 | ``` 145 | spatial local worker launch External cloud 146 | ``` 147 | 148 | ## Integrating with an existing project 149 | 150 | If you have an existing project, to add a new C++ worker to it: 151 | 152 | 1. Decide whether the worker you're adding will be used as a managed or 153 | external worker. 154 | 2. Copy the corresponding directory (e.g. `workers/Managed`) into the workers 155 | directory of your existing project. 156 | 3. In the worker project `CMakeLists.txt` set `SCHEMA_SOURCE_DIR` and 157 | `WORKER_SDK_DIR` to point to the CMake projects in your project that 158 | generate the corresponding targets and if the targets have different names 159 | from `Schema` and `WorkerSdk` also rename those. 160 | 4. Add it to the `workers` definition in your SpatialOS launch configuration 161 | (e.g. `default_launch.json`) 162 | --------------------------------------------------------------------------------