diff --git a/.gitignore b/.gitignore index 702aa80..e54d387 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ spatialos_worker_packages.json build/ logs/ workers/unity/external-default-unityclient.log +workers/unity/Assets/Plugins/Improbable/Editor/Generated/ +workers/unity/Assets/Plugins/Improbable/Editor/Generated.meta +workers/unity/Assets/Plugins/Improbable/Generated/ +workers/unity/Assets/Plugins/Improbable/Generated.meta +workers/unity/Assets/Plugins/Improbable/EntityPrefabs/ +workers/unity/Assets/Plugins/Improbable/EntityPrefabs.meta +workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.* diff --git a/README.md b/README.md index bb0b681..e50932f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -**Note: This project runs on SpatialOS version 12.2.1 and not the latest version which is 13.0.0.** - # PiratesTutorial ![Pirates Screenshot](pirates-screenshot.jpg) diff --git a/spatialos.json b/spatialos.json index 4117fe3..3132c09 100644 --- a/spatialos.json +++ b/spatialos.json @@ -1,8 +1,8 @@ { "name": "your_project_name_here", "project_version": "1.0.0", - "sdk_version": "12.2.1", + "sdk_version": "13.0.0", "dependencies": [ - {"name": "standard_library", "version": "12.2.1"} + {"name": "standard_library", "version": "13.0.0"} ] } diff --git a/workers/unity/.gitignore b/workers/unity/.gitignore index 14dfa34..784d328 100644 --- a/workers/unity/.gitignore +++ b/workers/unity/.gitignore @@ -1,14 +1,11 @@ # Improbable Unity Ignores .spatialos/ spatialos_worker_packages.json -build.json .invoke.log zip_toolbelt.log external-default-unityclient.log external-default-unityworker.log -Improbable.meta Assets/Improbable -Assets/Plugins/Improbable Assets/Plugins/x86/Improbable Assets/Plugins/x86.meta Assets/Plugins/x86_64/Improbable diff --git a/workers/unity/Assets/Plugins/Improbable.meta b/workers/unity/Assets/Plugins/Improbable.meta new file mode 100644 index 0000000..c28200e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: f326997a2d8c07e4d98274717c87cf09 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem.meta new file mode 100644 index 0000000..e75ad55 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: ac249586b46041340aee45d45b2c7718 +folderAsset: yes +timeCreated: 1516628439 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor.meta new file mode 100644 index 0000000..0a1b74c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a406347b4133bb2499a69a992a9d1526 +folderAsset: yes +timeCreated: 1515594091 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration.meta new file mode 100644 index 0000000..f090b32 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 875bc38aec3bbbd40addf5e19f18c87d +folderAsset: yes +timeCreated: 1516879369 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironment.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironment.cs new file mode 100644 index 0000000..7535145 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironment.cs @@ -0,0 +1,10 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + public enum BuildEnvironment + { + Local, + Cloud + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironment.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironment.cs.meta new file mode 100644 index 0000000..600cfee --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironment.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f0865b1f4f2046a4a480d9aef2bf6354 +timeCreated: 1515678457 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironmentConfig.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironmentConfig.cs new file mode 100644 index 0000000..b1f2610 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironmentConfig.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using UnityEditor; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + [Serializable] + public class BuildEnvironmentConfig + { + public SpatialBuildPlatforms BuildPlatforms = SpatialBuildPlatforms.Current; + public BuildOptions BuildOptions = 0; + + [NonSerialized] public bool ShowBuildOptions = true; + [NonSerialized] public bool ShowBuildPlatforms = true; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironmentConfig.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironmentConfig.cs.meta new file mode 100644 index 0000000..bc729c8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildEnvironmentConfig.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9b73d6e624a04708b19836efaa1b5c65 +timeCreated: 1515591603 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildPaths.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildPaths.cs new file mode 100644 index 0000000..925eeef --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildPaths.cs @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.IO; +using Improbable.Unity.Util; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + public static class BuildPaths + { + public static readonly string PrefabResourcesDirectory = + PathUtil.Combine("Assets", "Improbable", "Generated", "Resources", "EntityPrefabs").ToUnityPath(); + + public static readonly string PrefabSourceDirectory = PathUtil.Combine("Assets", "EntityPrefabs").ToUnityPath(); + + public static string BuildScratchDirectory + { + get { return Path.GetFullPath(Path.Combine("build", "worker")); } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildPaths.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildPaths.cs.meta new file mode 100644 index 0000000..4cd4164 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/BuildPaths.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fba50f74fb02ae5458452f0314f709ea +timeCreated: 1515413340 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/FallbackIndentLevelScope.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/FallbackIndentLevelScope.cs new file mode 100644 index 0000000..182f797 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/FallbackIndentLevelScope.cs @@ -0,0 +1,28 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +#if !UNITY_2017_3_OR_NEWER +using System; +using UnityEditor; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + /// + /// Fallback for Unity 2017.3f1 feature: EditorGUI.IndentLevelScope + /// + public class FallbackIndentLevelScope : IDisposable + { + private readonly int indent; + + public FallbackIndentLevelScope(int increment) + { + indent = EditorGUI.indentLevel; + EditorGUI.indentLevel += indent; + } + + public void Dispose() + { + EditorGUI.indentLevel = indent; + } + } +} +#endif diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/FallbackIndentLevelScope.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/FallbackIndentLevelScope.cs.meta new file mode 100644 index 0000000..87422a5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/FallbackIndentLevelScope.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: ad8fd511165f02a4b87eba70852032fe +timeCreated: 1516634254 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/GUIColorScope.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/GUIColorScope.cs new file mode 100644 index 0000000..9bef653 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/GUIColorScope.cs @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using UnityEngine; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + class GUIColorScope : IDisposable + { + private readonly Color color; + + public GUIColorScope(Color newColor) + { + color = GUI.color; + GUI.color = newColor; + } + + public void Dispose() + { + GUI.color = color; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/GUIColorScope.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/GUIColorScope.cs.meta new file mode 100644 index 0000000..d7f082a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/GUIColorScope.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: bd15769f3e8c3054cbd24cd67dc29714 +timeCreated: 1516892521 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/PlayerCompression.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/PlayerCompression.cs new file mode 100644 index 0000000..b26aa00 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/PlayerCompression.cs @@ -0,0 +1,13 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + /// + /// Indicate whether or not built-out players should be compressed. + /// + public enum PlayerCompression + { + Enabled, + Disabled + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/PlayerCompression.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/PlayerCompression.cs.meta new file mode 100644 index 0000000..8492902 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/PlayerCompression.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fc38f441295f4e238f5640a33fbb37c5 +timeCreated: 1515429509 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SceneItem.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SceneItem.cs new file mode 100644 index 0000000..e0081d6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SceneItem.cs @@ -0,0 +1,41 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + internal class SceneItem + { + public readonly SceneAsset SceneAsset; + public bool Included; + private readonly bool exists; + + public SceneItem(SceneAsset sceneAsset, bool included, SceneAsset[] inAssetDatabase) + { + SceneAsset = sceneAsset; + Included = included; + exists = inAssetDatabase.Contains(sceneAsset); + } + + public static SceneItem Drawer(Rect position, SceneItem item) + { + using (item.exists ? null : new GUIColorScope(Color.red)) + { + var positionWidth = position.width; + var labelWidth = GUI.skin.toggle.CalcSize(GUIContent.none).x + 5; + + position.width = labelWidth; + item.Included = EditorGUI.Toggle(position, item.Included); + + position.x += labelWidth; + position.width = positionWidth - labelWidth; + + EditorGUI.ObjectField(position, item.SceneAsset, typeof(SceneAsset), false); + } + + return item; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SceneItem.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SceneItem.cs.meta new file mode 100644 index 0000000..28bdc56 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SceneItem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d7b8c20e979c4107a4b48bd19da441ab +timeCreated: 1515691397 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObject.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObject.cs new file mode 100644 index 0000000..7344ef6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObject.cs @@ -0,0 +1,118 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + public abstract class SingletonScriptableObject : ScriptableObject + where TSelf : SingletonScriptableObject + { + private static readonly List Instances = + new List(); + + public virtual void OnEnable() + { + if (!IsAnAsset()) + { + // This is not an asset, so don't register it as an instance. + return; + } + + var self = (TSelf) this; + + if (Instances.Find(instance => instance != self)) + { + Debug.LogErrorFormat("There are multiple copies of {0} present. Please pick one and delete the other.", SelfType); + } + + if (!Instances.Contains(self)) + { + Instances.Add(self); + } + } + + protected bool IsAnAsset() + { + var assetPath = AssetDatabase.GetAssetPath(this); + + // If there is an asset path, it is in assets. + return !string.IsNullOrEmpty(assetPath); + } + + public void OnDisable() + { + if (!IsAnAsset()) + { + return; + } + + var self = (TSelf) this; + + if (Instances.Contains(self)) + { + Instances.Remove(self); + } + } + + private static readonly Type SelfType = typeof(TSelf); + + public static TSelf GetInstance() + { + // Clean up dead ones. + Instances.RemoveAll(item => item == null); + + if (Instances.Count > 0) + { + return Instances[0]; + } + + if (SingletonScriptableObjectLoader.LoadingInstances.Contains(SelfType)) + { + return null; + } + + SingletonScriptableObjectLoader.LoadingInstances.Add(SelfType); + + try + { + var allInstanceGuidsInAssetDatabase = + AssetDatabase.FindAssets("t:" + SelfType.Name); + + foreach (var instanceGUID in allInstanceGuidsInAssetDatabase) + { + var instancePath = AssetDatabase.GUIDToAssetPath(instanceGUID); + + var loadedInstance = AssetDatabase.LoadAssetAtPath(instancePath); + + // onload should have been called here, but if not, ensure it's in the list. + if (loadedInstance == null) + { + continue; + } + + if (Instances.Find(instance => instance != loadedInstance)) + { + Debug.LogErrorFormat( + "There are multiple copies of {0} present. Please pick one and delete the other.", + SelfType); + } + + if (!Instances.Contains(loadedInstance)) + { + Instances.Add(loadedInstance); + } + } + } + finally + { + SingletonScriptableObjectLoader.LoadingInstances.Remove(SelfType); + } + + return Instances.FirstOrDefault(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObject.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObject.cs.meta new file mode 100644 index 0000000..a093116 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObject.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1036cced36af4c0d977785b69e0d0761 +timeCreated: 1515593337 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObjectLoader.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObjectLoader.cs new file mode 100644 index 0000000..2e9338e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObjectLoader.cs @@ -0,0 +1,12 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + internal static class SingletonScriptableObjectLoader + { + internal static readonly HashSet LoadingInstances = new HashSet(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObjectLoader.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObjectLoader.cs.meta new file mode 100644 index 0000000..5423caa --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SingletonScriptableObjectLoader.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e5962adf05954b8787059c9052726a98 +timeCreated: 1515593328 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialBuildPlatforms.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialBuildPlatforms.cs new file mode 100644 index 0000000..9c0a62b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialBuildPlatforms.cs @@ -0,0 +1,14 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + [System.Flags] + public enum SpatialBuildPlatforms + { + Current = 1 << 0, + Windows32 = 1 << 1, + Windows64 = 1 << 2, + Linux = 1 << 3, + OSX = 1 << 4 + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialBuildPlatforms.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialBuildPlatforms.cs.meta new file mode 100644 index 0000000..e406f31 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialBuildPlatforms.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e0882977395e42ebab12de768f0489b2 +timeCreated: 1515604481 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfiguration.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfiguration.cs new file mode 100644 index 0000000..7a067ed --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfiguration.cs @@ -0,0 +1,143 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + [CreateAssetMenu(fileName = "SpatialOS Build Configuration", menuName = CreateMenuPath)] + public class SpatialOSBuildConfiguration : SingletonScriptableObject + { + internal const string CreateMenuPath = "SpatialOS/Build Configuration"; + + [SerializeField] private bool isInitialised; + + public WorkerBuildConfiguration[] WorkerBuildConfigurations; + + public override void OnEnable() + { + base.OnEnable(); + + if (!isInitialised) + { + ResetToDefault(); + } + + if (IsAnAsset()) + { + UpdateEditorScenesForBuild(); + } + } + + private void ResetToDefault() + { + // Build default settings + var client = new WorkerBuildConfiguration() + { + WorkerPlatform = WorkerPlatform.UnityClient, + ScenesForWorker = AssetDatabase.FindAssets("t:Scene") + .Select(AssetDatabase.GUIDToAssetPath) + .Where(path => path.Contains(WorkerPlatform.UnityClient.ToString())) + .Select(AssetDatabase.LoadAssetAtPath).ToArray(), + LocalBuildConfig = new BuildEnvironmentConfig() + { + BuildPlatforms = SpatialBuildPlatforms.Current, + BuildOptions = BuildOptions.Development + }, + CloudBuildConfig = new BuildEnvironmentConfig() + { + BuildPlatforms = SpatialBuildPlatforms.Current + } + }; + + var worker = new WorkerBuildConfiguration() + { + WorkerPlatform = WorkerPlatform.UnityWorker, + ScenesForWorker = AssetDatabase.FindAssets("t:Scene") + .Select(AssetDatabase.GUIDToAssetPath) + .Where(path => path.Contains(WorkerPlatform.UnityWorker.ToString())) + .Select(AssetDatabase.LoadAssetAtPath).ToArray(), + LocalBuildConfig = new BuildEnvironmentConfig() + { + BuildPlatforms = SpatialBuildPlatforms.Current, + BuildOptions = BuildOptions.EnableHeadlessMode + }, + CloudBuildConfig = new BuildEnvironmentConfig() + { + BuildPlatforms = SpatialBuildPlatforms.Linux, + BuildOptions = BuildOptions.EnableHeadlessMode + } + }; + + WorkerBuildConfigurations = new WorkerBuildConfiguration[] + { + client, worker + }; + + isInitialised = true; + } + + private void OnValidate() + { + if (!isInitialised) + { + ResetToDefault(); + } + + if (IsAnAsset()) + { + UpdateEditorScenesForBuild(); + } + } + + private SceneAsset[] GetScenesForWorker(WorkerPlatform workerPlatform) + { + WorkerBuildConfiguration configurationForWorker = null; + + if (WorkerBuildConfigurations != null) + { + configurationForWorker = + WorkerBuildConfigurations.FirstOrDefault(config => config.WorkerPlatform == workerPlatform); + } + + return configurationForWorker == null + ? new SceneAsset[0] + : configurationForWorker.ScenesForWorker; + } + + internal void UpdateEditorScenesForBuild() + { + EditorApplication.delayCall += () => + { + EditorBuildSettings.scenes = + GetScenesForWorker(WorkerPlatform.UnityClient) + .Union(GetScenesForWorker(WorkerPlatform.UnityWorker)) + .Select(AssetDatabase.GetAssetPath) + .Select(scenePath => new EditorBuildSettingsScene(scenePath, true)) + .ToArray(); + }; + } + + public BuildEnvironmentConfig GetEnvironmentConfigForWorker(WorkerPlatform platform, + BuildEnvironment targetEnvironment) + { + var config = WorkerBuildConfigurations.FirstOrDefault(x => x.WorkerPlatform == platform); + if (config == null) + { + throw new ArgumentException("Unknown WorkerPlatform " + platform); + } + + return config.GetEnvironmentConfig(targetEnvironment); + } + + public string[] GetScenePathsForWorker(WorkerPlatform workerType) + { + return GetScenesForWorker(workerType) + .Where(sceneAsset => sceneAsset != null) + .Select(AssetDatabase.GetAssetPath) + .ToArray(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfiguration.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfiguration.cs.meta new file mode 100644 index 0000000..cbcaebb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfiguration.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: add92caab7fb7f24589c2e7198c19491 +timeCreated: 1513698470 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfigurationEditor.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfigurationEditor.cs new file mode 100644 index 0000000..00e64c4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfigurationEditor.cs @@ -0,0 +1,419 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + [CustomEditor(typeof(SpatialOSBuildConfiguration))] + public class SpatialOSBuildConfigurationEditor : UnityEditor.Editor + { + private const int ScreenWidthForHorizontalLayout = 450; + + private SceneAsset[] scenesInAssetDatabase; + + public void OnEnable() + { + scenesInAssetDatabase = AssetDatabase.FindAssets("t:Scene") + .Select(AssetDatabase.GUIDToAssetPath) + .Select(AssetDatabase.LoadAssetAtPath).ToArray(); + } + + private bool scenesChanged; + + public override void OnInspectorGUI() + { + var workerConfiguration = (SpatialOSBuildConfiguration) target; + + scenesChanged = false; + + var configs = workerConfiguration.WorkerBuildConfigurations; + foreach (WorkerBuildConfiguration workerConfig in configs) + { + DrawWorkerConfiguration(workerConfig); + } + + if (scenesChanged) + { + scenesChanged = false; + workerConfiguration.UpdateEditorScenesForBuild(); + } + } + + private void DrawWorkerConfiguration(WorkerBuildConfiguration configurationForWorker) + { + var platformName = configurationForWorker.WorkerPlatform.ToString(); + configurationForWorker.ShowFoldout = EditorGUILayout.Foldout(configurationForWorker.ShowFoldout, platformName); + + if (configurationForWorker.ShowFoldout) + { + using (IndentLevelScope(1)) + { + DrawScenesInspectorForWorker(configurationForWorker); + DrawEnvironmentInspectorForWorker(configurationForWorker); + } + } + } + + [Flags] + private enum ReorderableListFlags + { + None = 0, + ShowIndices = 1 << 0, + EnableReordering = 1 << 1, + } + + private void DrawScenesInspectorForWorker(WorkerBuildConfiguration configurationForWorker) + { + EditorGUILayout.LabelField("Scenes", EditorStyles.boldLabel); + + using (IndentLevelScope(1)) + { + var scenesToShowInList = configurationForWorker + .ScenesForWorker + .Select((sceneAsset, index) => new SceneItem(sceneAsset, true, scenesInAssetDatabase)) + .ToList(); + + EditorGUI.BeginChangeCheck(); + + var sceneItems = scenesInAssetDatabase + .Where(sceneAsset => !configurationForWorker.ScenesForWorker.Contains(sceneAsset)) + .Select(sceneAsset => new SceneItem(sceneAsset, false, scenesInAssetDatabase)).ToList(); + + var horizontalLayout = Screen.width > ScreenWidthForHorizontalLayout; + + using (horizontalLayout ? new EditorGUILayout.HorizontalScope() : null) + { + using (horizontalLayout ? new EditorGUILayout.VerticalScope() : null) + { + EditorGUILayout.LabelField("Scenes to include (in order)"); + + DrawIndentedList(scenesToShowInList, ReorderableListFlags.ShowIndices | ReorderableListFlags.EnableReordering, + SceneItem.Drawer); + } + + using (horizontalLayout ? new EditorGUILayout.VerticalScope() : null) + { + using (horizontalLayout ? IndentLevelScope(-EditorGUI.indentLevel) : null) + { + EditorGUILayout.LabelField("Exclude"); + + DrawIndentedList(sceneItems, + ReorderableListFlags.None, + SceneItem.Drawer); + } + } + } + + if (EditorGUI.EndChangeCheck()) + { + EditorUtility.SetDirty(target); + Undo.RecordObject(target, "Configure scenes for worker"); + + configurationForWorker.ScenesForWorker = scenesToShowInList.Concat(sceneItems) + .Where(item => item.Included) + .Select(item => item.SceneAsset).ToArray(); + + scenesChanged = true; + } + } + } + + private void DrawEnvironmentInspectorForWorker(WorkerBuildConfiguration configurationForWorker) + { + EditorGUILayout.LabelField("Environments", EditorStyles.boldLabel); + + DrawEnvironmentInspector(BuildEnvironment.Local, configurationForWorker); + DrawEnvironmentInspector(BuildEnvironment.Cloud, configurationForWorker); + } + + private static TEnum EnumFlagsToggleField(TEnum source) where TEnum : struct, IConvertible + { + return EnumFlagsToggleField(source, SimpleToString); + } + + private static TEnum EnumFlagsToggleField(TEnum source, Func nameFunction) + where TEnum : struct, IConvertible + { + if (!typeof(TEnum).IsEnum) + { + throw new ArgumentException("TEnum must be an enum type"); + } + + var enumNonZeroValues = Enum.GetValues(typeof(TEnum)).Cast() + .Where(options => options.ToInt32(NumberFormatInfo.CurrentInfo) != 0).ToArray(); + + using (IndentLevelScope(1)) + { + var indentedHelpBox = new GUIStyle(EditorStyles.helpBox) { margin = { left = EditorGUI.indentLevel * 16 } }; + + using (new EditorGUILayout.VerticalScope(indentedHelpBox)) + using (IndentLevelScope(-EditorGUI.indentLevel)) + { + var sourceBitValue = source.ToInt32(NumberFormatInfo.CurrentInfo); + + foreach (var enumValue in enumNonZeroValues) + { + var targetBitValue = enumValue.ToInt32(NumberFormatInfo.CurrentInfo); + + var hasFlag = (sourceBitValue & targetBitValue) != 0; + + var newFlag = EditorGUILayout.ToggleLeft(nameFunction(enumValue), hasFlag); + + if (hasFlag != newFlag) + { + // the flag has changed, doing an XOR will flip that value + source = (TEnum) (object) (sourceBitValue ^ targetBitValue); + } + } + } + } + + return source; + } + + private void DrawEnvironmentInspector(BuildEnvironment environment, + WorkerBuildConfiguration configurationForWorker) + { + using (IndentLevelScope(1)) + { + EditorGUILayout.LabelField(environment.ToString()); + + var environmentConfiguration = + configurationForWorker.GetEnvironmentConfig(environment); + + ConfigureBuildPlatforms(environmentConfiguration); + ConfigureBuildOptions(environmentConfiguration); + } + } + + private void ConfigureBuildOptions(BuildEnvironmentConfig environmentConfiguration) + { + using (IndentLevelScope(1)) + { + var buildOptionsString = EnumFlagToString(environmentConfiguration.BuildOptions); + + EditorGUI.BeginChangeCheck(); + + var showBuildOptions = EditorGUILayout.Foldout(environmentConfiguration.ShowBuildOptions, + "Build Options: " + buildOptionsString); + + BuildOptions newBuildOptions = environmentConfiguration.BuildOptions; + + if (showBuildOptions) + { + newBuildOptions = EnumFlagsToggleField(environmentConfiguration.BuildOptions); + } + + if ((newBuildOptions & BuildOptions.EnableHeadlessMode) != 0 && + (newBuildOptions & BuildOptions.Development) != 0) + { + EditorGUILayout.HelpBox( + "\nYou cannot have both EnableHeadlessMode and Development in BuildOptions.\n\n" + + "This will crash Unity Editor while building.\n", + MessageType.Error); + } + + if (EditorGUI.EndChangeCheck()) + { + // build options have changed + + EditorUtility.SetDirty(target); + Undo.RecordObject(target, "Configure build options for worker"); + + environmentConfiguration.ShowBuildOptions = showBuildOptions; + environmentConfiguration.BuildOptions = newBuildOptions; + } + } + } + + private static string EnumFlagToString(TEnum value) where TEnum : struct, IConvertible + { + return EnumFlagToString(value, SimpleToString); + } + + private static string SimpleToString(TValue activeValue) where TValue : IConvertible + { + return activeValue.ToString(CultureInfo.InvariantCulture); + } + + private static string EnumFlagToString(TEnum value, Func nameFunction) + where TEnum : struct, IConvertible + { + if (!typeof(TEnum).IsEnum) + { + throw new ArgumentException("TEnum must be an enum type"); + } + + var enumNonZeroValues = Enum.GetValues(typeof(TEnum)).Cast() + .Where(options => options.ToInt32(NumberFormatInfo.CurrentInfo) != 0).ToArray(); + + var sourceBitValue = value.ToInt32(NumberFormatInfo.CurrentInfo); + + if (sourceBitValue == 0) + { + return "None"; + } + + return string.Join(", ", + enumNonZeroValues + .Where(enumValue => (sourceBitValue & enumValue.ToInt32(NumberFormatInfo.CurrentInfo)) != 0) + .Select(nameFunction).ToArray()); + } + + private void ConfigureBuildPlatforms(BuildEnvironmentConfig environmentConfiguration) + { + using (IndentLevelScope(1)) + { + var buildPlatformsString = EnumFlagToString(environmentConfiguration.BuildPlatforms, + BuildPlatformToString); + + EditorGUI.BeginChangeCheck(); + + var showBuildPlatforms = EditorGUILayout.Foldout(environmentConfiguration.ShowBuildPlatforms, + "Build Platforms: " + buildPlatformsString); + + SpatialBuildPlatforms newBuildPlatforms = environmentConfiguration.BuildPlatforms; + + if (showBuildPlatforms) + { + newBuildPlatforms = EnumFlagsToggleField(environmentConfiguration.BuildPlatforms, + BuildPlatformToString); + } + + var currentAdjustedPlatforms = newBuildPlatforms; + + if ((currentAdjustedPlatforms & SpatialBuildPlatforms.Current) != 0) + { + currentAdjustedPlatforms &= ~SpatialBuildPlatforms.Current; + currentAdjustedPlatforms |= WorkerBuilder.GetCurrentBuildPlatform(); + } + + if ((currentAdjustedPlatforms & SpatialBuildPlatforms.Windows32) != 0 && + (currentAdjustedPlatforms & SpatialBuildPlatforms.Windows64) != 0) + { + EditorGUILayout.HelpBox( + "\n" + WorkerBuilder.IncompatibleWindowsPlatformsErrorMessage + "\n", + MessageType.Error); + } + + if (EditorGUI.EndChangeCheck()) + { + // build platforms have changed + EditorUtility.SetDirty(target); + Undo.RecordObject(target, "Configure build platforms for worker"); + + environmentConfiguration.ShowBuildPlatforms = showBuildPlatforms; + environmentConfiguration.BuildPlatforms = newBuildPlatforms; + } + } + } + + private static string BuildPlatformToString(SpatialBuildPlatforms value) + { + if (value == SpatialBuildPlatforms.Current) + { + return string.Format("Current ({0})", WorkerBuilder.GetCurrentBuildPlatform()); + } + + return value.ToString(); + } + + private static readonly GUIContent MoveUpButtonContents = new GUIContent("^", "Move item up"); + private static readonly GUIContent MoveDownButtonContents = new GUIContent("v", "Move item down"); + + private static void DrawIndentedList(IList list, + ReorderableListFlags flags, Func drawer) + { + if (list.Count == 0) + { + EditorGUILayout.HelpBox("No items in list", MessageType.Info); + return; + } + + var enableReordering = (flags & ReorderableListFlags.EnableReordering) != 0; + var showIndices = (flags & ReorderableListFlags.ShowIndices) != 0; + var indentLevel = EditorGUI.indentLevel; + + using (IndentLevelScope(-EditorGUI.indentLevel)) + { + for (var i = 0; i < list.Count; i++) + { + var item = list[i]; + var controlRect = EditorGUILayout.GetControlRect(false); + + using (IndentLevelScope(indentLevel)) + { + controlRect = EditorGUI.IndentedRect(controlRect); + } + + if (showIndices) + { + var indexContent = new GUIContent(i.ToString()); + var indexRect = new Rect(controlRect); + indexRect.width = GUI.skin.label.CalcSize(indexContent).x; + + GUI.Label(indexRect, indexContent); + + controlRect.x += indexRect.width + 5; + controlRect.width -= indexRect.width + 5; + } + + var drawerRect = new Rect(controlRect); + + if (enableReordering) + { + drawerRect.width -= 40; + } + + drawer(drawerRect, item); + + if (enableReordering) + { + var buttonRect = new Rect(controlRect); + buttonRect.x = buttonRect.xMax - 40; + buttonRect.width = 20; + + using (new EditorGUI.DisabledScope(i == 0)) + { + if (GUI.Button(buttonRect, MoveUpButtonContents)) + { + SwapInList(list, i, i - 1); + } + } + + buttonRect.x += 20; + + using (new EditorGUI.DisabledScope(i == list.Count - 1)) + { + if (GUI.Button(buttonRect, MoveDownButtonContents)) + { + SwapInList(list, i, i + 1); + } + } + } + } + } + } + + private static void SwapInList(IList list, int indexA, int indexB) + { + var temp = list[indexA]; + list[indexA] = list[indexB]; + list[indexB] = temp; + } + + private static IDisposable IndentLevelScope(int increment) + { +#if UNITY_2017_3_OR_NEWER + return new EditorGUI.IndentLevelScope(increment); +#else + return new FallbackIndentLevelScope(increment); +#endif + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfigurationEditor.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfigurationEditor.cs.meta new file mode 100644 index 0000000..1092087 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/SpatialOSBuildConfigurationEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5b7f35f2a6dae6b43baacb662601de3d +timeCreated: 1513779752 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildConfiguration.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildConfiguration.cs new file mode 100644 index 0000000..e7f022a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildConfiguration.cs @@ -0,0 +1,38 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using UnityEditor; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + [Serializable] + public class WorkerBuildConfiguration + { + public WorkerPlatform WorkerPlatform; + public SceneAsset[] ScenesForWorker; + + public BuildEnvironmentConfig LocalBuildConfig = new BuildEnvironmentConfig(); + public BuildEnvironmentConfig CloudBuildConfig = new BuildEnvironmentConfig(); + + public BuildEnvironmentConfig GetEnvironmentConfig(BuildEnvironment targetEnvironment) + { + BuildEnvironmentConfig buildEnvironmentConfig; + + switch (targetEnvironment) + { + case BuildEnvironment.Local: + buildEnvironmentConfig = LocalBuildConfig; + break; + case BuildEnvironment.Cloud: + buildEnvironmentConfig = CloudBuildConfig; + break; + default: + throw new ArgumentOutOfRangeException("targetEnvironment", targetEnvironment, null); + } + + return buildEnvironmentConfig; + } + + [NonSerialized] public bool ShowFoldout = true; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildConfiguration.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildConfiguration.cs.meta new file mode 100644 index 0000000..f3de497 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildConfiguration.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 635648f6704340679b4166330a3d2c97 +timeCreated: 1513698790 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildData.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildData.cs new file mode 100644 index 0000000..79d783a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildData.cs @@ -0,0 +1,85 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Util; +using UnityEditor; + +namespace Improbable.Unity.MinimalBuildSystem.Configuration +{ + public class WorkerBuildData + { + internal const BuildTarget OSXBuildTarget = +#if UNITY_2017_3_OR_NEWER + BuildTarget.StandaloneOSX; +#else + BuildTarget.StandaloneOSXIntel64; +#endif + + private readonly WorkerPlatform workerPlatform; + private readonly BuildTarget buildTarget; + + private static readonly Dictionary BuildTargetNames = + new Dictionary + { + { BuildTarget.StandaloneWindows, "Windows" }, + { BuildTarget.StandaloneWindows64, "Windows" }, + { BuildTarget.StandaloneLinux64, "Linux" }, + { OSXBuildTarget, "Mac" } + }; + + private static readonly Dictionary BuildPlatformExtensions = + new Dictionary + { + { BuildTarget.StandaloneWindows, ".exe" }, + { BuildTarget.StandaloneWindows64, ".exe" }, + { BuildTarget.StandaloneLinux64, "" }, + { OSXBuildTarget, "" } + }; + + public WorkerBuildData(WorkerPlatform workerPlatform, BuildTarget buildTarget) + { + switch (workerPlatform) + { + case WorkerPlatform.UnityWorker: + case WorkerPlatform.UnityClient: + break; + default: + throw new ArgumentException("Unsupported WorkerPlatform " + workerPlatform); + } + + if (!BuildTargetNames.ContainsKey(buildTarget)) + { + throw new ArgumentException("Unsupported BuildPlatform " + workerPlatform); + } + + this.workerPlatform = workerPlatform; + this.buildTarget = buildTarget; + } + + private string BuildTargetName + { + get { return BuildTargetNames[buildTarget]; } + } + + public string BuildScratchDirectory + { + get { return PathUtil.Combine(BuildPaths.BuildScratchDirectory, PackageName, ExecutableName).ToUnityPath(); } + } + + public string WorkerPlatformName + { + get { return workerPlatform.ToString(); } + } + + private string ExecutableName + { + get { return PackageName + BuildPlatformExtensions[buildTarget]; } + } + + public string PackageName + { + get { return string.Format("{0}@{1}", workerPlatform, BuildTargetName); } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildData.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildData.cs.meta new file mode 100644 index 0000000..4619e4f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/Configuration/WorkerBuildData.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: cd14637da710af84ab5725fefe45fb37 +timeCreated: 1515422041 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/EntityPrefabs.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/EntityPrefabs.cs new file mode 100644 index 0000000..33299b9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/EntityPrefabs.cs @@ -0,0 +1,93 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Improbable.Unity.Assets; +using Improbable.Unity.MinimalBuildSystem.Configuration; +using Improbable.Unity.Util; +using UnityEditor; +using UnityEditor.Build; +using UnityEngine; + +namespace Improbable.Unity.MinimalBuildSystem +{ + public static class EntityPrefabs + { + public static void Export(WorkerPlatform platform) + { + Clean(); + + var prefabPaths = GetAllPrefabAssetPaths(); + var copies = CopyPrefabs(prefabPaths); + CompilePrefabs(copies, platform); + + AssetDatabase.SaveAssets(); + Resources.UnloadUnusedAssets(); + } + + public static void Clean() + { + FileUtil.DeleteFileOrDirectory(BuildPaths.PrefabResourcesDirectory); + FileUtil.DeleteFileOrDirectory(BuildPaths.PrefabResourcesDirectory + ".meta"); + + AssetDatabase.Refresh(); + } + + private static void CompilePrefabs(List paths, WorkerPlatform platform) + { + var progress = 0; + foreach (var path in paths) + { + EditorUtility.DisplayProgressBar("Processing EntityPrefabs", "Preparing", + progress / (float) paths.Count()); + progress++; + + var compiler = new PrefabCompiler(platform); + var prefab = AssetDatabase.LoadAssetAtPath(path); + compiler.Compile(prefab); + } + + EditorUtility.ClearProgressBar(); + } + + private static List CopyPrefabs(List paths) + { + AssetDatabase.StartAssetEditing(); + PathUtil.EnsureDirectoryExists(BuildPaths.PrefabResourcesDirectory); + List newPaths = new List(); + var progress = 0; + + try + { + foreach (var path in paths) + { + EditorUtility.DisplayProgressBar("Processing EntityPrefabs", "Preparing", + progress / (float) paths.Count()); + progress++; + + var targetPath = PathUtil.Combine(BuildPaths.PrefabResourcesDirectory, Path.GetFileName(path)); + if (!AssetDatabase.CopyAsset(path, targetPath)) + { + throw new BuildFailedException("Failed to process EntityPrefab " + path); + } + + newPaths.Add(targetPath); + } + } + finally + { + AssetDatabase.StopAssetEditing(); + EditorUtility.ClearProgressBar(); + } + + return newPaths; + } + + private static List GetAllPrefabAssetPaths() + { + return AssetDatabase.FindAssets("t:prefab", new[] { BuildPaths.PrefabSourceDirectory }) + .Select(AssetDatabase.GUIDToAssetPath).ToList(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/EntityPrefabs.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/EntityPrefabs.cs.meta new file mode 100644 index 0000000..4e90126 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/EntityPrefabs.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e65897c3f389c284996fd9e8e0cec76e +timeCreated: 1492805719 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/MinimalBuildSystemMenu.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/MinimalBuildSystemMenu.cs new file mode 100644 index 0000000..8ead430 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/MinimalBuildSystemMenu.cs @@ -0,0 +1,73 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using Improbable.Unity.MinimalBuildSystem.Configuration; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.MinimalBuildSystem +{ + static class MinimalBuildSystemMenu + { + private const string MinimalBuildMenu = "Improbable/Experimental Build"; + + [MenuItem(MinimalBuildMenu + "/Build UnityClient for local", false, 1)] + public static void BuildLocalClient() + { + MenuBuild(new[] { WorkerPlatform.UnityClient }, BuildEnvironment.Local); + } + + [MenuItem(MinimalBuildMenu + "/Build UnityWorker for local", false, 2)] + public static void BuildLocalWorker() + { + MenuBuild(new[] { WorkerPlatform.UnityWorker }, BuildEnvironment.Local); + } + + [MenuItem(MinimalBuildMenu + "/Build all workers for local", false, 3)] + public static void BuildLocalAll() + { + MenuBuild(new[] { WorkerPlatform.UnityWorker, WorkerPlatform.UnityClient }, BuildEnvironment.Local); + } + + [MenuItem(MinimalBuildMenu + "/Build UnityClient for cloud", false, 14)] + public static void BuildCloudClient() + { + MenuBuild(new[] { WorkerPlatform.UnityClient }, BuildEnvironment.Cloud); + } + + [MenuItem(MinimalBuildMenu + "/Build UnityWorker for cloud", false, 15)] + public static void BuildCloudWorker() + { + MenuBuild(new[] { WorkerPlatform.UnityWorker }, BuildEnvironment.Cloud); + } + + [MenuItem(MinimalBuildMenu + "/Build all workers for cloud", false, 16)] + public static void BuildCloudAll() + { + MenuBuild(new[] { WorkerPlatform.UnityClient, WorkerPlatform.UnityWorker }, BuildEnvironment.Cloud); + } + + [MenuItem(MinimalBuildMenu + "/Clean all workers", false, 27)] + public static void Clean() + { + WorkerBuilder.Clean(); + Debug.Log("Clean completed"); + } + + private static void MenuBuild(IEnumerable platforms, BuildEnvironment environment) + { + // Delaying build by a frame to ensure the editor has re-rendered the UI to avoid odd glitches. + EditorApplication.delayCall += () => + { + Debug.Log("Generating build configuration"); + SpatialCommands.GenerateBuildConfiguration(); + foreach (var platform in platforms) + { + WorkerBuilder.BuildWorkerForEnvironment(platform, environment); + } + + Debug.LogFormat("Completed build for {0} target", environment); + }; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/MinimalBuildSystemMenu.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/MinimalBuildSystemMenu.cs.meta new file mode 100644 index 0000000..44869a8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/MinimalBuildSystemMenu.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c867eb2bf6ee4ac8b437eba48bc1cfab +timeCreated: 1513953870 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommandRunner.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommandRunner.cs new file mode 100644 index 0000000..647b33a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommandRunner.cs @@ -0,0 +1,34 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Diagnostics; +using Improbable.Unity.Editor.Addons; + +namespace Improbable.Unity.MinimalBuildSystem +{ + internal static class SpatialCommandRunner + { + internal static void RunSpatialCommand(string args, string description) + { + var process = SpatialCommand.RunCommandWithSpatialInThePath(SpatialCommand.SpatialPath, new ProcessStartInfo + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + FileName = SpatialCommand.SpatialPath, + Arguments = args, + CreateNoWindow = true + }); + + var output = process.StandardOutput.ReadToEnd(); + var errOut = process.StandardError.ReadToEnd(); + process.WaitForExit(); + if (process.ExitCode != 0) + { + throw new Exception(string.Format( + "Could not {2}. The following error occurred: {0}, {1}\n", output, + errOut, description)); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommandRunner.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommandRunner.cs.meta new file mode 100644 index 0000000..659cab0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommandRunner.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dc9c0a87316d45478a2dbfcf2bf38875 +timeCreated: 1515584015 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommands.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommands.cs new file mode 100644 index 0000000..78acb64 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommands.cs @@ -0,0 +1,51 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.IO; +using Improbable.Unity.MinimalBuildSystem.Configuration; +using Improbable.Unity.Util; + +namespace Improbable.Unity.MinimalBuildSystem +{ + internal static class SpatialCommands + { + #region Build Configuration + + internal static void GenerateBuildConfiguration() + { + SpatialCommandRunner.RunSpatialCommand("build build-config", "generate build configuration"); + } + + #endregion Build Configuration + + #region Zip + + internal static void Zip(string zipAbsolutePath, string basePath, PlayerCompression compression) + { + ZipThroughSpatial(zipAbsolutePath, basePath, compression); + } + + private static void ZipThroughSpatial(string zipAbsolutePath, string basePath, PlayerCompression useCompression) + { + var zipFileFullPath = Path.GetFullPath(zipAbsolutePath); + + SpatialCommandRunner.RunSpatialCommand(ZipArgs(basePath, zipFileFullPath, useCompression), + string.Format("package the folder {0}", basePath)); + } + + private static string ZipArgs(string basePath, string zipFileFullPath, + PlayerCompression useCompression) + { + var filePattern = "**"; + var subFolder = ""; + + return string.Format( + "file zip --output=\"{0}\" --basePath=\"{1}\" --relativePath=. \"{2}\" --compression={3}", + zipFileFullPath, + Path.GetFullPath(basePath), + PathUtil.EnsureTrailingSlash(subFolder) + filePattern, + useCompression == PlayerCompression.Enabled); + } + + #endregion Zip + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommands.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommands.cs.meta new file mode 100644 index 0000000..ee335f1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/SpatialCommands.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 05a6667f572f4f3c8ccd27d1c14941bb +timeCreated: 1515584173 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/WorkerBuilder.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/WorkerBuilder.cs new file mode 100644 index 0000000..366651b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/WorkerBuilder.cs @@ -0,0 +1,266 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Improbable.Editor.Configuration; +using Improbable.Unity.EditorTools.Util; +using Improbable.Unity.MinimalBuildSystem.Configuration; +using Improbable.Unity.Util; +using UnityEditor; +using UnityEditor.Build; +using UnityEngine; + +namespace Improbable.Unity.MinimalBuildSystem +{ + public static class WorkerBuilder + { + private static readonly string PlayerBuildDirectory = + Path.GetFullPath(PathUtil.Combine(Directory.GetCurrentDirectory(), EditorPaths.AssetDatabaseDirectory, + "worker")); + + /// + /// Build method that is invoked by commandline + /// + public static void Build() + { + try + { + var commandLine = Environment.GetCommandLineArgs(); + + Debug.LogFormat("Want to build with args: {0}", String.Join(", ", commandLine)); + + var buildTargetArg = + CommandLineUtil.GetCommandLineValue(commandLine, "buildTarget", "local"); + + if (string.IsNullOrEmpty(buildTargetArg)) + { + // The default above does not get filled when -t parameter is not passed + buildTargetArg = BuildEnvironment.Local.ToString(); + Debug.LogWarningFormat("Using default build target value: \"{0}\".", buildTargetArg); + } + + BuildEnvironment buildEnvironment; + + switch (buildTargetArg.ToLower()) + { + case "cloud": + buildEnvironment = BuildEnvironment.Cloud; + break; + case "local": + buildEnvironment = BuildEnvironment.Local; + break; + default: + throw new BuildFailedException("Unknown build target value: " + buildTargetArg); + } + + var workerTypesArg = + CommandLineUtil.GetCommandLineValue(commandLine, ConfigNames.BuildWorkerTypes, + "UnityClient,UnityWorker"); + + var wantedWorkerPlatforms = GetWorkerPlatforms(workerTypesArg); + + SpatialCommands.GenerateBuildConfiguration(); + + foreach (var workerPlatform in wantedWorkerPlatforms) + { + BuildWorkerForEnvironment(workerPlatform, buildEnvironment); + } + } + catch (Exception e) + { + // Log the exception so it appears in the command line, and rethrow as a BuildFailedException so the build fails. + Debug.LogException(e); + + if (e is BuildFailedException) + { + throw; + } + + throw new BuildFailedException(e); + } + } + + internal static WorkerPlatform[] GetWorkerPlatforms(string workerTypesArg) + { + WorkerPlatform[] wantedWorkerPlatforms; + + try + { + wantedWorkerPlatforms = workerTypesArg.ToLower().Split(',') + .Select(workerType => + { + switch (workerType.Trim()) + { + case "unityclient": + return WorkerPlatform.UnityClient; + case "unityworker": + return WorkerPlatform.UnityWorker; + default: + throw new ArgumentException( + string.Format( + "The value '{0}' does not match any expected values: 'unityclient' or 'unityworker'.", + workerType)); + } + }) + .Distinct() + .ToArray(); + + Array.Sort(wantedWorkerPlatforms); + } + catch (ArgumentException innerException) + { + throw new ArgumentException(string.Format("Invalid argument: +{0} {1}\n{2}", + ConfigNames.BuildWorkerTypes, workerTypesArg, innerException.Message)); + } + + return wantedWorkerPlatforms; + } + + public static void BuildWorkerForEnvironment(WorkerPlatform workerPlatform, BuildEnvironment targetEnvironment) + { + var spatialOSBuildConfiguration = GetBuildConfiguration(); + var environmentConfig = + spatialOSBuildConfiguration.GetEnvironmentConfigForWorker(workerPlatform, targetEnvironment); + var buildPlatforms = environmentConfig.BuildPlatforms; + var buildOptions = environmentConfig.BuildOptions; + + PathUtil.EnsureDirectoryExists(PlayerBuildDirectory); + + foreach (var unityBuildTarget in GetUnityBuildTargets(buildPlatforms)) + { + BuildWorkerForTarget(workerPlatform, unityBuildTarget, buildOptions, targetEnvironment); + } + } + + internal const string IncompatibleWindowsPlatformsErrorMessage = + "Please choose only one of Windows32 or Windows64 as a build platform."; + + internal static IEnumerable GetUnityBuildTargets(SpatialBuildPlatforms actualPlatforms) + { + List result = new List(); + + if ((actualPlatforms & SpatialBuildPlatforms.Current) != 0) + { + actualPlatforms |= GetCurrentBuildPlatform(); + } + + if ((actualPlatforms & SpatialBuildPlatforms.Linux) != 0) + { + result.Add(BuildTarget.StandaloneLinux64); + } + + if ((actualPlatforms & SpatialBuildPlatforms.OSX) != 0) + { + result.Add(WorkerBuildData.OSXBuildTarget); + } + + if ((actualPlatforms & SpatialBuildPlatforms.Windows32) != 0) + { + if ((actualPlatforms & SpatialBuildPlatforms.Windows64) != 0) + { + throw new Exception(IncompatibleWindowsPlatformsErrorMessage); + } + + result.Add(BuildTarget.StandaloneWindows); + } + + if ((actualPlatforms & SpatialBuildPlatforms.Windows64) != 0) + { + result.Add(BuildTarget.StandaloneWindows64); + } + + return result.ToArray(); + } + + private static void BuildWorkerForTarget(WorkerPlatform workerPlatform, BuildTarget buildTarget, + BuildOptions buildOptions, BuildEnvironment targetEnvironment) + { + var spatialOSBuildConfiguration = GetBuildConfiguration(); + + Debug.LogFormat("Building \"{0}\" for worker platform: \"{1}\", environment: \"{2}\"", buildTarget, + workerPlatform, targetEnvironment); + + EntityPrefabs.Export(workerPlatform); + + var symbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone); + + try + { + var workerBuildData = new WorkerBuildData(workerPlatform, buildTarget); + var scenes = spatialOSBuildConfiguration.GetScenePathsForWorker(workerPlatform); + + var typeSymbol = "IMPROBABLE_WORKERTYPE_" + workerBuildData.WorkerPlatformName.ToUpper(); + var workerSymbols = symbols.Split(';') + .Concat(new[] { typeSymbol }) + .Distinct() + .Aggregate((current, next) => current + ";" + next); + PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, workerSymbols); + + BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions + { + options = buildOptions, + target = buildTarget, + scenes = scenes, + locationPathName = workerBuildData.BuildScratchDirectory + }; + + BuildPipeline.BuildPlayer(buildPlayerOptions); + + var zipPath = Path.GetFullPath(Path.Combine(PlayerBuildDirectory, workerBuildData.PackageName)); + + var basePath = PathUtil.Combine(BuildPaths.BuildScratchDirectory, workerBuildData.PackageName); + + SpatialCommands.Zip(zipPath, basePath, + targetEnvironment == BuildEnvironment.Local + ? PlayerCompression.Disabled + : PlayerCompression.Enabled); + } + finally + { + PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, symbols); + EntityPrefabs.Clean(); + } + } + + internal const string BuildConfigurationMissingErrorMessage = + "No objects of type SpatialOSBuildConfiguration found in the project.\nPlease create one using Assets/Create/" + + SpatialOSBuildConfiguration.CreateMenuPath + "."; + + /// An instance of SpatialOSBuildConfiguration if one exists. + /// If no assets exist of type SpatialOSBuildConfiguration + private static SpatialOSBuildConfiguration GetBuildConfiguration() + { + var spatialOSBuildConfiguration = SpatialOSBuildConfiguration.GetInstance(); + + if (spatialOSBuildConfiguration == null) + { + throw new Exception(BuildConfigurationMissingErrorMessage); + } + + return spatialOSBuildConfiguration; + } + + internal static SpatialBuildPlatforms GetCurrentBuildPlatform() + { + switch (Application.platform) + { + case RuntimePlatform.WindowsEditor: + return SpatialBuildPlatforms.Windows64; + case RuntimePlatform.OSXEditor: + return SpatialBuildPlatforms.OSX; + case RuntimePlatform.LinuxEditor: + return SpatialBuildPlatforms.Linux; + default: + throw new Exception("Unsupported platform detected: " + Application.platform); + } + } + + public static void Clean() + { + FileUtil.DeleteFileOrDirectory(PlayerBuildDirectory); + FileUtil.DeleteFileOrDirectory(BuildPaths.BuildScratchDirectory); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/WorkerBuilder.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/WorkerBuilder.cs.meta new file mode 100644 index 0000000..5047ee9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Editor/WorkerBuilder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b5b3752654112eb4e99f5ac2f71db12d +timeCreated: 1513951010 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime.meta new file mode 100644 index 0000000..7422b42 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b3facd901a528c14283ce8dce6655787 +folderAsset: yes +timeCreated: 1515593925 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/BasicTemplateProvider.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/BasicTemplateProvider.cs new file mode 100644 index 0000000..b588359 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/BasicTemplateProvider.cs @@ -0,0 +1,75 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Assets; +using Improbable.Unity.Assets; +using Improbable.Unity.Configuration; +using Improbable.Unity.Core; +using Improbable.Unity.Entity; +using UnityEngine; + +namespace Improbable.MinimalBuildSystem.Prefabs +{ + public class BasicTemplateProvider : MonoBehaviour, IEntityTemplateProvider + { + // These can be overridden on the command line. + public bool UseLocalPrefabs = true; + + // The template provider can't be instantiated during construction as Application.isEditor doesn't work. + private IEntityTemplateProvider templateProvider; + + private IEntityTemplateProvider TemplateProvider + { + get + { + if (templateProvider == null) + { + var gameObjectLoader = InitializeAssetLoader(); + templateProvider = InitializeTemplateProvider(gameObjectLoader); + } + + return templateProvider; + } + } + + private IAssetLoader InitializeAssetLoader() + { +#if UNITY_EDITOR + UseLocalPrefabs = + SpatialOS.Configuration.GetCommandLineValue(CommandLineConfigNames.UseLocalPrefabs, UseLocalPrefabs); + if (UseLocalPrefabs) + { + return new PrefabGameObjectLoader(); + } +#endif + return new ResourceGameObjectLoader(); + } + + private IEntityTemplateProvider InitializeTemplateProvider(IAssetLoader gameObjectLoader) + { +#if UNITY_EDITOR + if (UseLocalPrefabs) + { + return new AssetDatabaseTemplateProvider( + new CachingAssetDatabase(new PreprocessingGameObjectLoader(gameObjectLoader))); + } +#endif + return new AssetDatabaseTemplateProvider(new CachingAssetDatabase(gameObjectLoader)); + } + + public void PrepareTemplate(string prefabName, Action onSuccess, Action onError) + { + TemplateProvider.PrepareTemplate(prefabName, onSuccess, onError); + } + + public GameObject GetEntityTemplate(string prefabName) + { + return TemplateProvider.GetEntityTemplate(prefabName); + } + + public void CancelAllTemplatePreparations() + { + TemplateProvider.CancelAllTemplatePreparations(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/BasicTemplateProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/BasicTemplateProvider.cs.meta new file mode 100644 index 0000000..39680f1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/BasicTemplateProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6210cd2fa437e0e45a8867eaffb36e9c +timeCreated: 1513334970 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/ResourceGameObjectLoader.cs b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/ResourceGameObjectLoader.cs new file mode 100644 index 0000000..54e89c6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/ResourceGameObjectLoader.cs @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Assets; +using UnityEngine; + +namespace Improbable.MinimalBuildSystem.Prefabs +{ + public class ResourceGameObjectLoader : IAssetLoader + { + public void LoadAsset(string prefabName, Action onAssetLoaded, Action onError) + { + var gameObject = Resources.Load("EntityPrefabs/" + prefabName); + if (gameObject == null) + { + onError(new Exception("Prefab not found: " + prefabName)); + } + else + { + onAssetLoaded(gameObject); + } + } + + public void CancelAllLoads() + { + // LoadAsset is instantaneous, so no need to do anything here. + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/ResourceGameObjectLoader.cs.meta b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/ResourceGameObjectLoader.cs.meta new file mode 100644 index 0000000..4aa9fe9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.Modules/MinimalBuildSystem/Runtime/ResourceGameObjectLoader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f614dc3930838624f97d4c36b63b4ca6 +timeCreated: 1513336271 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math.meta b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math.meta new file mode 100644 index 0000000..79d5dc7 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 38fabc5c0ace6d040b77db18e18e54bc +folderAsset: yes +timeCreated: 1497005776 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/CoordinatesUnityPartial.cs b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/CoordinatesUnityPartial.cs new file mode 100644 index 0000000..4c1c0b4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/CoordinatesUnityPartial.cs @@ -0,0 +1,184 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using UnityEngine; + +namespace Improbable +{ + public partial struct Coordinates + { + /// + /// A Coordinates with 0 in each dimension. + /// + public static readonly Coordinates ZERO = new Coordinates(0, 0, 0); + + public static Vector3d operator -(Coordinates v1, Coordinates v2) + { + return new Vector3d(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + public static Coordinates operator -(Coordinates v1, Vector3d v2) + { + return new Coordinates(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + public static Coordinates operator +(Coordinates a, Vector3d b) + { + return new Coordinates(a.x + b.x, a.y + b.y, a.z + b.z); + } + + public static Coordinates operator -(Coordinates v1, Vector3f v2) + { + return new Coordinates(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + public static Coordinates operator +(Coordinates a, Vector3f b) + { + return new Coordinates(a.x + b.x, a.y + b.y, a.z + b.z); + } + + public static Coordinates operator -(Coordinates v1, Vector3 v2) + { + return new Coordinates(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + public static Coordinates operator +(Coordinates a, Vector3 b) + { + return new Coordinates(a.x + b.x, a.y + b.y, a.z + b.z); + } + + public static Coordinates operator -(Vector3d v1, Coordinates v2) + { + return new Coordinates(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + public static Coordinates operator -(Vector3f v1, Coordinates v2) + { + return new Coordinates(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + public static Coordinates operator -(Vector3 v1, Coordinates v2) + { + return new Coordinates(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z); + } + + public static Coordinates operator +(Vector3d a, Coordinates b) + { + return new Coordinates(a.x + b.x, a.y + b.y, a.z + b.z); + } + + public static Coordinates operator +(Vector3f a, Coordinates b) + { + return new Coordinates(a.x + b.x, a.y + b.y, a.z + b.z); + } + + public static Coordinates operator +(Vector3 a, Coordinates b) + { + return new Coordinates(a.x + b.x, a.y + b.y, a.z + b.z); + } + + /// + /// Interpolates between to global coordinates + /// + /// The starting position + /// The position to intepolate towards + /// + /// where 0 is the currentPosition and 1 is newPosition. Note: the value is clamped between 0 + /// and 1 to prevent extrapolation. + /// + /// The interpolated position + public static Coordinates Lerp(Coordinates currentPosition, Coordinates newPosition, float progressRatio) + { + progressRatio = progressRatio > 1 ? 1 : (progressRatio < 0 ? 0 : progressRatio); // Clamp + var valueDelta = newPosition - currentPosition; + return valueDelta * progressRatio + currentPosition; + } + + public double X + { + get { return x; } + set { x = value; } + } + + public double Y + { + get { return y; } + set { y = value; } + } + + public double Z + { + get { return z; } + set { z = value; } + } + + /// + /// Check if a coordinate is near another. + /// + /// The coordinate to test. + /// The allowed range. + /// True if the other coordinate is within strictly less than the specified range. + public bool IsWithinDistance(Coordinates other, double distance) + { +#pragma warning disable 618 + return IsWithinSquareDistance(other, distance * distance); +#pragma warning restore 618 + } + + /// + /// Calculate the square-space distance between two coordinates. + /// + /// The square-space distance between two coordinates. + public static double SquareDistance(Coordinates v1, Coordinates v2) + { + return (v1 - v2).SquareMagnitude(); + } + + /// + /// Check if a coordinate is near another. + /// + /// The coordinate to test. + /// The allowed square-space range. + /// True if the other coordinate is within strictly less than the specified range. + public bool IsWithinSquareDistance(Coordinates other, double sqrDistance) + { + return SquareDistance(this, other) < sqrDistance; + } + + /// + /// True if all components of the Coordinates are real numbers. + /// + public bool IsFinite + { + get + { + return !double.IsNaN(x) && !double.IsNaN(y) && !double.IsNaN(z) && + !double.IsInfinity(x) && !double.IsInfinity(y) && !double.IsInfinity(z); + } + } + + /// + /// Converts to a Unity Vector3. + /// + public Vector3 ToUnityVector() + { + return new Vector3((float) x, (float) y, (float) z); + } + + /// + /// Converts to a Spatial Vector3d. + /// + public Vector3d ToSpatialVector3d() + { + return new Vector3d(x, y, z); + } + + /// + /// Returns the string representation of the Coordinates. + /// + public override string ToString() + { + return "Coordinates(" + x + ", " + y + ", " + z + ")"; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/CoordinatesUnityPartial.cs.meta b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/CoordinatesUnityPartial.cs.meta new file mode 100644 index 0000000..0bc8ca7 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/CoordinatesUnityPartial.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e6c7cfc31026aff44bf3f6d8c89b4861 +timeCreated: 1496998314 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3dUnityPartial.cs b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3dUnityPartial.cs new file mode 100644 index 0000000..c74d814 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3dUnityPartial.cs @@ -0,0 +1,123 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using UnityEngine; + +namespace Improbable +{ + public partial struct Vector3d + { + /// + /// A Vector3d with 0 in each dimension. + /// + public static readonly Vector3d ZERO = new Vector3d(0, 0, 0); + + /// + /// Override of the multiplication opporator. Used for multiplying the Vector3d by a float scalar. + /// + public static Vector3d operator *(Vector3d vector3d, double scalar) + { + return new Vector3d(vector3d.x * scalar, vector3d.y * scalar, vector3d.z * scalar); + } + + /// + /// Override of the multiplication opporator. Used for multiplying the Vector3d by a float scalar. + /// + public static Vector3d operator *(double scalar, Vector3d vector3d) + { + return new Vector3d(vector3d.x * scalar, vector3d.y * scalar, vector3d.z * scalar); + } + + /// + /// Override of the division opporator. Used for dividing the Vector3d by a float scalar. + /// + public static Vector3d operator /(Vector3d vector3d, double scalar) + { + return new Vector3d(vector3d.x / scalar, vector3d.y / scalar, vector3d.z / scalar); + } + + /// + /// Override of the addition opporator. Used for adding two Vector3s. + /// + public static Vector3d operator +(Vector3d vector3d, Vector3d addvector3d) + { + return new Vector3d(vector3d.x + addvector3d.x, vector3d.y + addvector3d.y, vector3d.z + addvector3d.z); + } + + /// + /// Override of the subtraction opporator. Used for subtracting one Vector3d from another. + /// + public static Vector3d operator -(Vector3d vector3d, Vector3d subtractVector3d) + { + return new Vector3d(vector3d.x - subtractVector3d.x, vector3d.y - subtractVector3d.y, vector3d.z - subtractVector3d.z); + } + + /// + /// Computes the square of the magnitude of the Vector3d. + /// + public double SquareMagnitude() + { + return x * x + y * y + z * z; + } + + /// + /// Returns the normal of the Vector3d (does not modify the original Vector3f). + /// + public Vector3d Normalized() + { + var magnitude = System.Math.Sqrt(SquareMagnitude()); + return new Vector3d(x / magnitude, y / magnitude, z / magnitude); + } + + public double X + { + get { return x; } + } + + public double Y + { + get { return y; } + } + + public double Z + { + get { return z; } + } + + /// + /// True if all components of the Vector3d are real numbers. + /// + public bool IsFinite + { + get + { + return !double.IsNaN(x) && !double.IsNaN(y) && !double.IsNaN(z) && + !double.IsInfinity(x) && !double.IsInfinity(y) && !double.IsInfinity(z); + } + } + + /// + /// Converts the Vector3d to a Unity Vector3. + /// + public Vector3 ToUnityVector() + { + return new Vector3((float) x, (float) y, (float) z); + } + + /// + /// Converts the Vector3d to a Unity Quaternion. + /// + public Quaternion ToUnityQuaternion() + { + return Quaternion.Euler(ToUnityVector()); + } + + /// + /// Returns the string representation of the Vector3d. + /// + public override string ToString() + { + return "Vector3d(" + x + ", " + y + ", " + z + ")"; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3dUnityPartial.cs.meta b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3dUnityPartial.cs.meta new file mode 100644 index 0000000..395950f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3dUnityPartial.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 192c9bb92bd23504fb1eeaab4df209ea +timeCreated: 1496998314 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3fUnityPartial.cs b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3fUnityPartial.cs new file mode 100644 index 0000000..e59e527 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3fUnityPartial.cs @@ -0,0 +1,123 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using UnityEngine; + +namespace Improbable +{ + public partial struct Vector3f + { + /// + /// A Vector3f with 0f in each dimension. + /// + public static readonly Vector3f ZERO = new Vector3f(0f, 0f, 0f); + + /// + /// Override of the multiplication opporator. Used for multiplying the Vector3f by a float scalar. + /// + public static Vector3f operator *(Vector3f vector3f, float scalar) + { + return new Vector3f(vector3f.x * scalar, vector3f.y * scalar, vector3f.z * scalar); + } + + /// + /// Override of the multiplication opporator. Used for multiplying the Vector3f by a float scalar. + /// + public static Vector3f operator *(float scalar, Vector3f vector3f) + { + return new Vector3f(vector3f.x * scalar, vector3f.y * scalar, vector3f.z * scalar); + } + + /// + /// Override of the division opporator. Used for dividing the Vector3f by a float scalar. + /// + public static Vector3f operator /(Vector3f vector3f, float scalar) + { + return new Vector3f(vector3f.x / scalar, vector3f.y / scalar, vector3f.z / scalar); + } + + /// + /// Override of the addition opporator. Used for adding two Vector3s. + /// + public static Vector3f operator +(Vector3f vector3f, Vector3f addVector3f) + { + return new Vector3f(vector3f.x + addVector3f.x, vector3f.y + addVector3f.y, vector3f.z + addVector3f.z); + } + + /// + /// Override of the subtraction opporator. Used for subtracting one Vector3f from another. + /// + public static Vector3f operator -(Vector3f vector3f, Vector3f subtractVector3f) + { + return new Vector3f(vector3f.x - subtractVector3f.x, vector3f.y - subtractVector3f.y, vector3f.z - subtractVector3f.z); + } + + /// + /// Computes the square of the magnitude of the Vector3f. + /// + public float SquareMagnitude() + { + return x * x + y * y + z * z; + } + + /// + /// Returns the normal of the Vector3f (does not modify the original Vector3f). + /// + public Vector3f Normalized() + { + var magnitude = (float) System.Math.Sqrt(SquareMagnitude()); + return new Vector3f(x / magnitude, y / magnitude, z / magnitude); + } + + public float X + { + get { return x; } + } + + public float Y + { + get { return y; } + } + + public float Z + { + get { return z; } + } + + /// + /// True if all components of the Vector3f are real numbers. + /// + public bool IsFinite + { + get + { + return !float.IsNaN(x) && !float.IsNaN(y) && !float.IsNaN(z) && + !float.IsInfinity(x) && !float.IsInfinity(y) && !float.IsInfinity(z); + } + } + + /// + /// Converts the Vector3f to a Unity Vector3. + /// + public Vector3 ToUnityVector() + { + return new Vector3(x, y, z); + } + + /// + /// Converts the Vector3f to a Unity Quaternion. + /// + public Quaternion ToUnityQuaternion() + { + return Quaternion.Euler(ToUnityVector()); + } + + /// + /// Returns the string representation of the Vector3f. + /// + public override string ToString() + { + return "Vector3f(" + x + ", " + y + ", " + z + ")"; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3fUnityPartial.cs.meta b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3fUnityPartial.cs.meta new file mode 100644 index 0000000..749d0eb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/.UnityPartials/Math/Vector3fUnityPartial.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 81f000d9b57440e46bfde4f0dbc79879 +timeCreated: 1496998314 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core.meta b/workers/unity/Assets/Plugins/Improbable/Core.meta new file mode 100644 index 0000000..aa2b0e9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 8bb4021c6123b494e8d163021363b220 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Linux.meta b/workers/unity/Assets/Plugins/Improbable/Core/Linux.meta new file mode 100644 index 0000000..147eaea --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Linux.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 2e778f30959d283429868b08dd5be1fc +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins.meta b/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins.meta new file mode 100644 index 0000000..70afe06 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: baf20348710c14f4e96179ebf7b5b75b +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins/x86_64.meta b/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins/x86_64.meta new file mode 100644 index 0000000..05bc9b2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins/x86_64.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 673b9cc2e58656547b1b030bedecd15e +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins/x86_64/libCoreSdkDll.so b/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins/x86_64/libCoreSdkDll.so new file mode 100644 index 0000000..2ecd388 Binary files /dev/null and b/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins/x86_64/libCoreSdkDll.so differ diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins/x86_64/libCoreSdkDll.so.meta b/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins/x86_64/libCoreSdkDll.so.meta new file mode 100644 index 0000000..c52528d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Linux/Plugins/x86_64/libCoreSdkDll.so.meta @@ -0,0 +1,87 @@ +fileFormatVersion: 2 +guid: c7da83f6aa260a7488cc31842380f697 +timeCreated: 1526049940 +licenseType: Pro +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + '': OSXIntel + second: + enabled: 0 + settings: + CPU: None + - first: + '': OSXIntel64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: x86_64 + DefaultValueInitialized: true + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Facebook: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: LinuxUniversal + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/OSX.meta b/workers/unity/Assets/Plugins/Improbable/Core/OSX.meta new file mode 100644 index 0000000..228a70a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/OSX.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: d1031e26f23afed43bb3ad7feef58f92 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle.meta b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle.meta new file mode 100644 index 0000000..2c709c2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle.meta @@ -0,0 +1,42 @@ +fileFormatVersion: 2 +guid: beed007e08ec5e74b9ce017361061751 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + '': OSXIntel + second: + enabled: 1 + settings: {} + - first: + '': OSXIntel64 + second: + enabled: 1 + settings: {} + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + DefaultValueInitialized: true + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents.meta b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents.meta new file mode 100644 index 0000000..f91e993 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 423ed5cbd20219349bf143381ae64f99 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/Info.plist b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/Info.plist new file mode 100644 index 0000000..0aab70e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/Info.plist @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + CoreSdkDll + CFBundleGetInfoString + + CFBundleIconFile + + CFBundleIdentifier + + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + + CFBundleName + + CFBundlePackageType + APPL + CFBundleShortVersionString + + CFBundleSignature + ???? + CFBundleVersion + + CSResourcesFileMapped + + NSHumanReadableCopyright + + NSPrincipalClass + NSApplication + NSHighResolutionCapable + True + + diff --git a/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/Info.plist.meta b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/Info.plist.meta new file mode 100644 index 0000000..c301877 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/Info.plist.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 79ff8ad7eefbb8c4988509d091dcc37e +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/MacOS.meta b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/MacOS.meta new file mode 100644 index 0000000..9a4eee2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/MacOS.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: bca08ae668e2354458867858c8ef86cb +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/MacOS/CoreSdkDll b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/MacOS/CoreSdkDll new file mode 100644 index 0000000..83c4350 Binary files /dev/null and b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/MacOS/CoreSdkDll differ diff --git a/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/MacOS/CoreSdkDll.meta b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/MacOS/CoreSdkDll.meta new file mode 100644 index 0000000..fff3fd3 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/OSX/CoreSdkDll.bundle/Contents/MacOS/CoreSdkDll.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 47880e36d5d6c63489f1cd2f41154136 +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows.meta b/workers/unity/Assets/Plugins/Improbable/Core/Windows.meta new file mode 100644 index 0000000..7eaf844 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Windows.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: aa0c0ab22821aa4469237eecd83f459d +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins.meta b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins.meta new file mode 100644 index 0000000..72b7265 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 6232c8ec78b8314408b1a9e89cf3daa9 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86.meta b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86.meta new file mode 100644 index 0000000..15ced74 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 0695e9ee3bdcf154ea1805742482d347 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.dll b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.dll new file mode 100644 index 0000000..4d3e379 Binary files /dev/null and b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.dll differ diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.dll.meta b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.dll.meta new file mode 100644 index 0000000..8820086 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.dll.meta @@ -0,0 +1,87 @@ +fileFormatVersion: 2 +guid: de819717a5c44914e95e9e8f5c474fd3 +timeCreated: 1526049940 +licenseType: Pro +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + '': OSXIntel + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + '': OSXIntel64 + second: + enabled: 0 + settings: + CPU: None + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: x86 + DefaultValueInitialized: true + - first: + Facebook: Win + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Linux + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: LinuxUniversal + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.lib b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.lib new file mode 100644 index 0000000..6cb6580 Binary files /dev/null and b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.lib differ diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.lib.meta b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.lib.meta new file mode 100644 index 0000000..a0ae1d4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86/CoreSdkDll.lib.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9e3f9addf9eb37b40bcdec98ed959e8b +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64.meta b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64.meta new file mode 100644 index 0000000..2ab55b4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 3888a8c27eedc3245966f839caeac8ba +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.dll b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.dll new file mode 100644 index 0000000..25cead0 Binary files /dev/null and b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.dll differ diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.dll.meta b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.dll.meta new file mode 100644 index 0000000..e45eeff --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.dll.meta @@ -0,0 +1,87 @@ +fileFormatVersion: 2 +guid: 3b3671b8866b82e44bc3c73c32c9c623 +timeCreated: 1526049940 +licenseType: Pro +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + '': OSXIntel + second: + enabled: 0 + settings: + CPU: None + - first: + '': OSXIntel64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: x86_64 + DefaultValueInitialized: true + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Facebook: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: LinuxUniversal + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.lib b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.lib new file mode 100644 index 0000000..896d6f4 Binary files /dev/null and b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.lib differ diff --git a/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.lib.meta b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.lib.meta new file mode 100644 index 0000000..5652b21 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Core/Windows/Plugins/x86_64/CoreSdkDll.lib.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: cd065eb1d5e6f134f8d37dcc3bc75c3b +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Editor.meta b/workers/unity/Assets/Plugins/Improbable/Editor.meta new file mode 100644 index 0000000..5b64c85 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Editor.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 01d4e8251b7728f4db095d688dd7446f +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Generated.meta b/workers/unity/Assets/Plugins/Improbable/Generated.meta new file mode 100644 index 0000000..3ded760 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Generated.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: acaae33012c48e44c91c9f1f2dbe5ead +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk.meta b/workers/unity/Assets/Plugins/Improbable/Sdk.meta new file mode 100644 index 0000000..a516d6a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 5f2c987cbd973ca4b863014c0be23ff5 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll.meta new file mode 100644 index 0000000..7244e44 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 26332946ed4bba54ba8da78b86cffde5 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.dll b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.dll new file mode 100644 index 0000000..dba0954 Binary files /dev/null and b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.dll differ diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.dll.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.dll.meta new file mode 100644 index 0000000..8176660 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.dll.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: e2945882351da4f98b2442bfa5fa02f6 +timeCreated: 1525947628 +licenseType: Pro +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.xml b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.xml new file mode 100644 index 0000000..58847e6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.xml @@ -0,0 +1,440 @@ + + + + Improbable.WorkerSdkCsharp.Framework + + + + + Object for sending asynchronous responses to command requests. + Upon receiving an incoming command request, a user-defined callback containing a user-submitted strategy for responding to the request is invoked. + + + + + Type definition: user defined callback function returning a command response given a command request. + + + + + Constructor method that sets the callback field. + + + + + Respond to an incoming command request asynchronously by invoking the user-defined callback. + The logic defined in the callback needs to eventually lead to an invokation of responseHandle.Respond(). + + + + + Interface abstracting evidence class for a command on a component. + + Command metaclass + Payload of the Command + Return type of the Command + + + + Creates an ICommandRequest object appropriate for the command's request type. + + + + + Creates an ICommandResponse object appropriate for the command's response type. + + + + + Extracts the command response from ICommandResponse object. + + + + + Extracts the command response from ICommandResponse object. + + + + + Internal name of the command to be invoked. + + + + + Evidence class for a command on a component. + + + Payload of the Command + Return type of the Command + + + + ComponentId of the component the command should be invoked on. + + + + + Internal name of the command to be invoked. + + + + + Creates an ICommandRequest object appropriate for the command's request type. + + + + + Creates an ICommandResponse object appropriate for the command's response type. + + + + + Extracts the command request from ICommandResponse object. + + + + + Extracts the command response from ICommandResponse object. + + + + + + + + + + + + + + + + + Marks an interface as the Reader Interface for use by visualizers. + + + + + Marks an interface as the Writer Interface for use by visualizers. + + + + + Associates a unique component Id with a specific interface. + + + + + The unique identifier. + + + + + Creates an instance of the attribute. + + + + + Metadata for the incoming command request. + + + + + Creates an instance of CommandRequestMetadata. + + + + + + + + + + + Contains callbacks that are invoked in response to events registered by calling + IComponentFactory.RegisterWithConnection(). + + + + + Contains an object with no callbacks specified. + + + + + Invoked after a component has been added. + + + Provides the EntityId the component belongs to, the metaclass of the component, + and the concrete implementation of the component. + + + + + Invoked after a component has been removed. + + + Provides the EntityId the component belongs to, the metaclass of the component, + and the concrete implementation of the component. + + + + + Invoked after authority over the component has been granted or revoked. + + + Provides the EntityId the component belongs to, the metaclass of the component, + whether or authority is granted (true) or revoked (false), and the concrete + implementation of the component. + + + + + Invoked after the component has been updated. + + + Provides the EntityId the component belongs to, the meta class of the component, + and the concrete implementation of the component. + + + + + Handles callbacks for events. + + + + + + + + + + + + Invokes callbacks with every member of a given object list. + + + + + + Invokes callbacks with a given data item. + + + + + + Object holding an ICommandResponder. + Exposed to the user by calling [component name].Writer.On[command name]. + Defines the user facing interface for registering/deregistering synchronous/asynchronous command response strategies. + + + + + Register a user-defined synchronous callback for responding to an incoming command request. + A CommandResponderWrapper can only have one ICommandResponder registered at the same time. + + + If a response is already registered. + + + + + Register a user-defined asynchronous callback for responding to an incoming command request. + A CommandResponderWrapper can only have one ICommandResponder registered at the same time. + + + If a response is already registered. + + + + + De-register a user-defined callback for responding to an incoming command request. + + + If no response is registered. + + + + + Invokes the commandResponder member fields SendResponse() method upon receiving a command request to send a response. + + + + + Interface type for objects that respond to incoming command requests given a user-defined callback. + + + + + Send a response to an incoming command request given a ResponseHandle corresponding to said request. + + + + + Handles callbacks for events. + + + + + + Registers an event callback. + + + + + Removes a registered event callback. + + + + + Handles user-registered callbacks. + + + + + + Creates an instance of UserCallbackHandler. + + + + + + + + + This class provides a stable, reference-counted memoiser. While the reference count for a particular + computation is > 0, the returned results of Add and Remove will return the same object as a result. + As the reference count is explicit, each call to Add must be matched with a call to Remove. Not calling + Remove will cause the class to not clean up its references. Calling Remove without an Add results in an error. + + + + + Metadata for the incoming command request. + + + + + WorkerId of the caller. + + + + + Attribute set of the caller. + + + + + An interface that allows for component meta classes to receive events + on a per-entity basis. + + + + + Call this once for each associated meta class to receive callbacks. + These callbacks will be called for the lifetime of the dispatcher object. + + + + + Call this once for each associated meta class when the connection is disposed of. + + + + + Returns an instance of the derived metaclass associated with the entity. + + + + + This method no longer does anything and should not be used. + Removes all event handlers from the instance associated with entityId. + + + + + An interface for writers, allowing identification of them as an (entity, component) pair. + + + + + The unique component Id. + + + + + The entityId associated with the writer instance. + + + + + Handles property changed callbacks. + + + + + + Registers a property callback. + + + + + Registers a property callback and invokes it immediately with the current value. + + + + + + Removes a registered property callback. + + + + + Object for sending synchronous responses to command requests. + Upon receiving an incoming command request, a response is composed with the help of a user-defined callback and sent back to the requester immediately. + + + + + Type definition: user defined callback function returning a command response given a command request. + + + + + Constructor method that sets the callback field. + + + + + Respond to an incoming command request synchronously by sending back a command response immediately. + + + + + Object passed to objects handling the command requests. + + + + + Payload sent by the entity sending the command. + + + + + Metadata for this command invocation instance. + + + + + Creates a ResponseHandle. Called from generated code. + + + + + Sends the response to command request. + + + + + diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.xml.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.xml.meta new file mode 100644 index 0000000..a2c05a2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.Framework.xml.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 4abc765c01cbd4d3a9d44dfa4d32a22e +timeCreated: 1525947639 +licenseType: Pro +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.dll b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.dll new file mode 100644 index 0000000..04eb516 Binary files /dev/null and b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.dll differ diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.dll.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.dll.meta new file mode 100644 index 0000000..75ff248 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.dll.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 75459aafa1dd9482c88d918a1b2ecac0 +timeCreated: 1525947627 +licenseType: Pro +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.xml b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.xml new file mode 100644 index 0000000..8667646 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.xml @@ -0,0 +1,1652 @@ + + + + Improbable.WorkerSdkCsharp + + + + + A unique identifier used to look up entity in SpatialOS. + + + Instances of this type should be treated as a transient identifiers that will not be + consistent between different runs of the same simulation. + + + + + An invalid entity ID. Note that this just one of many invalid entity IDs. + + + + + + The value of the EntityId. + + + Though this value is numeric, you should not perform any mathematical operations on it. + + + + + Constructs a new instance of an EntityId. + + + + + Whether this represents a valid SpatialOS entity ID. Specifically, Id > 0. + + True iff valid. + + + + Whether the specified entity ID is invalid. + + + An entity ID. + True iff entityId is invalid + + + + Whether the specified entity ID is valid. + + + An entity ID. + True iff entityId is valid + + + + + + + + + + Returns true if entityId1 is exactly equal to entityId2. + + + + + Returns true if entityId1 is not exactly equal to entityId2. + + + + + + + + + + + This class maintains a per-type cache of DeepCopier objects for efficiency. + This helps to avoid performing multiple runtime type checks and keeps the + logic of creating DeepCopier objects all in the same place. + + + + + This class is the same as System.Collections.Generic.List, except that it is + augmented with proper structural equality and GetHashCode. + + + + + Returns a deep copy of this list. Checks if the types in the list are either + primitive or implement the IDeepCopyable interface, then performs a + deep copy of the list if they are and throws an invalid operation exception + otherwise. If the list is empty it will return a new list. + + + + + Very similar to the usual C# Dictionary. However: iteration order is guaranteed to be + identical to insertion order; the dictionary can be traversed without allocation using + the First and Last properties; and it properly implements structural + equality and GetHashCode. + + + + + Returns a deep copy of this map. Checks if the types in the map are either + primitive or implement the IDeepCopyable interface, then performs a + deep copy of the map if they are and throws an invalid operation exception + otherwise. If the map is empty it will return a new map. + + + + + Represents an optional value. Works with both value types (in which case it behaves + similarly to a nullable type) and non-value types. Only allocates if a value is present. + + + + + Creates an Option containing the given value. + + + + + Implicit conversion from a value to an Option containing that value. + + + + + Implicit conversion from null to an empty Option (for value types). + + + + + Returns a deep copy of this option. Checks if the type in the option is either + primitive or implements the IDeepCopyable interface, then performs a + deep copy of the option if it is and throws an invalid operation exception + otherwise. If the option is empty it will return a new option. + + + + + Returns 1 if this Option contains a value, and 0 otherwise. + + + + + Removes the value from this Option, if present. + + + + + Assigns the value contained in this Option, replacing the existing value (if + present). + + + + + Returns true and assigns to value if this Option contains a value; + returns false otherwise. + + + + + Returns true if this Option contains a value, and false + otherwise. + + + + + Returns the value stored in this Option. Throws InvalidOperationException + if no value is present. + + + + + A wrapper for a byte array, with structural equality and hash code. + + + This is used by generated schema types to represent bytes fields. + + + + + Creates a new Bytes object wrapping a copy of the given byte array. + + + + + Creates a new Bytes object wrapping a copy of the given native byte buffer. + + + + + Creates a new Bytes object directly wrapping the given byte array. + + + Warning: since this method does not copy the array, modifications made to the source + array will be reflected by this Bytes object. In particular, it is illegal + to modify the source array concurrently with other operations on this object. + + + + + Returns a copy of the byte array wrapped by this Bytes object. + + + + + Directly returns the backing byte array wrapped by this Bytes object. + + + Warning: since this method does not copy the array, modifications made to the returned + array will be reflected by this Bytes object. In particular, it is illegal + to modify the source array concurrently with other operations on this object. + + + + + + + + + + + + + + + + + Exposes a global hook to be invoked when errors are detected in client-side code. This is + generally used to indicate programmer error, and covers cases such as attempting to throw + an exception from a Dispatcher callback. + + + + + Sets the Action to be invoked when a client exception is detected. + + + + + Invokes the global client error hook to handle an exception. + + + + + Interface representing a particular component. The generated code will contain a metaclass + for each component in the schema. These metaclasses are used to identify components; in + particular they are given as the generic type parameter to various component-related classes + and methods. + + + + + The ID of this component. + + + + + This is an implementation detail provided by generated code. + + + Clients should not need to call this method. + + + + + This is an implementation detail provided by generated code. + + + Clients should not need to call this method. + + + + + Represents data-at-rest for the component identified by the metaclass C. Each component data + has an extension method Get() that returns the concrete data type. + + + + + Converts the at-rest data type to an update. + + + + + Represents an update for the component identified by the metaclass C. Each component update + has an extension method Get() that returns the concrete update type. + + + + + Converts the update to an at-rest data type. Calling this method is only valid if the + update provides a value for each field in the component; otherwise an exception is + thrown. + + + + + Applies this update to the corresponding at-rest data type for the component. + + + + + Interface representing a command for a particular component. The generated code for a + component will contain a command metaclass for each command defined by the component. + These metaclasses are used to identify commands; in particular they are given as the generic + type parameter to various command-related classes and methods. + + + + + Represents a request for the command identified by the metaclass C. Each command request has + an extension method Get() that returns the concrete request type. + + + + + This is an implementation detail provided by generated code. + + + Clients should not need to call this method. + + + + + Represents a response for the command identified by the metaclass C. Each command response + has an extension method Get() that returns the concrete response type. + + + + + This is an implementation detail provided by generated code. + + + Clients should not need to call this method. + + + + + An opaque list of operations retrieved from Connection::GetOpList(). It is usually passed to + Dispatcher::Process(), which dispatches the operations to the appropriate callbacks. + + + + + + + + Overrides the default interest settings for a particular entity and component. + + + + Controls whether checkout is explicitly enabled or disabled. + + + + Parameters used to alter the behaviour of a command request. + + + + + Allow command requests to bypass the bridge when this worker is authoritative over the target + entity-component. + + + + + Worker Connection API. This is the main way of connecting to SpatialOS, processing + operations, and sending component updates. + + + This object should not be used concurrently by multiple threads. + + + + + Connects to a SpatialOS deployment via a receptionist. This is the flow used to connect + a managed worker running in the cloud alongside the deployment, and also to connect any + local worker to a (local or remote) deployment via a locally-running receptionist. + + + The hostname and port would typically be provided by SpatialOS on the command-line, if + this is a managed worker on the cloud, or otherwise be predetermined (e.g. + localhost:7777 for the default receptionist of a locally-running deployment). + + + + + + + + Returns true if the Connection object was created correctly and has successfully + connected to SpatialOS. + + + + + Retrieves the list of operations that have occurred since the last call to this + function. + + + If timeoutMillis is non-zero, the function will block until there is at least one + operation to return, or the timeout has been exceeded. If the timeout is exceeded, an + empty list will be returned. If timeoutMillis is zero the function is non-blocking. + + + + + Returns the ID that was assigned to this worker at runtime. + + + + + Returns the attributes associated with this worker at runtime. + + + + + Sends a log message for the worker to SpatialOS. + + + + + Sends a set of metrics for the worker to SpatialOS. Typically this function should be + called periodically (e.g. once every second) to report the worker's status. Since + histogram metrics are diff-based, calling this function clears each histogram in the + Metrics parameter. + + + + + Requests SpatialOS to reserve an entity ID. Returns a request ID, which can be used to identify a + response to the request via the Dispatcher.OnReserveEntityIdResponse callback. + + + If timeout_millis is not specified, the default timeout will be used. + + + + + Requests SpatialOS to reserve a batch of entity IDs. Returns a request ID, which can be + used to identify a response to the request via the Dispatcher.OnReserveEntityIdResponse + callback. + + + If timeout_millis is not specified, the default timeout will be used. + + + + + Requests SpatialOS to create an entity. Returns a request ID, which can be used to identify + response to the request via the Dispatcher.OnCreateEntityResponse callback. + + + If an entity ID is provided, it must have been reserved using SendReserveEntityIdRequest(). + If timeout_millis is not specified, the default timeout will be used. + + + + + Requests SpatialOS to delete an entity. Returns a request ID, which can be used to + identify a response to the request via the Dispatcher.OnDeleteEntityResponse callback. + + + If timeout_millis is not specified, the default timeout will be used. + + + + + Queries SpatialOS for remote entity data. Returns a request Id, which can be used to + identify a response to the request via the Dispatcher.OnEntityQueryResponse callback. + + + If timeout_millis is not specified, the default timeout will be used. + + + + + Sends a component interest update for the given entity to SpatialOS. By default, the + worker receives data for all entities according to the default component interests + specified in its bridge settings. This function overrides the default to explicitly + add or remove interest for particular components. + + + Interest for components not present in the interestOverrides map is unaffected. Note + also that components over which the worker is authoritative are always received, + regardless of interest settings. + + + + + Sends an acknowledgement of the receipt of an AuthorityLossImminent authority change for a + component. Sending the acknowledgement signifies that this worker is ready to lose authority + over the component. + + + + + Sends an update for an entity's component to SpatialOS. Note that the sent component + update is added as an operation to the operation list and will be returned by a + subsequent call to GetOpList(). + + + The behaviour is undefined if the update is mutated after it is sent; use + SendComponentUpdate(update.DeepCopy()) if you intend to hold on to the update and + modify it later. The legacyCallbackSemantics parameter is deprecated and exists for + legacy compatibility only. It will be removed soon and should not be used. + + + + + Sends a command request to a component on a specific target entity. Returns a request + ID which can be used to identify a response to the command via the + Dispatcher.OnCommandResponse callback. + + + If timeoutMillis is not specified, the default timeout will be used. Like + SendComponentUpdate, the behaviour is undefined if the request is mutated after it is + sent; SendCommandRequest(request.DeepCopy()) if you intend to modify the object + later. + + + + + Sends a response to an incoming command request for a component on an entity over which + this worker has authority. The request ID should match an incoming command request via + the Dispatcher.OnCommandRequest callback. + + + Like SendComponentUpdate, the behaviour is undefined if the response is mutated after it + is sent; SendCommandResponse(response.DeepCopy()) if you intend to modify the + object later. + + + + + Explicitly fails an incoming command request for a component on an entity over which + this worker has authority. The request ID should match an incoming command request via + the Dispatcher.OnCommandRequest callback. + + + The calling worker will receive a command response with status code + StatusCode.ApplicationError. + + + + + Enables or disables protocol logging. Logging uses the parameters specified when the Connection + was created. Enabling it when already enabled, or disabling it when already disabled, do + nothing. + + + Note that logs from any previous protocol logging sessions will be overwritten. + + + + The request was successfully executed and returned a response. + + + + The request timed out before a response was received. It can be retried, but carefully - + this usually means the deployment is overloaded, so some sort of backoff should be used + to avoid making the problem worse. This can also be caused by the target worker's + handling code failing to respond to the command at all, perhaps due to a bug in its + implementation. + + + + + The target entity did not exist, or did not have the target component. This probably + means the entity either hasn't been created yet or has already been deleted. It might + make sense to retry the request if there is reason to believe the entity hasn't yet been + created but will be soon. + + + + + The request could not be executed by a worker, either because it lost authority while + handling the request, or because no worker was authoritative at all. Assuming the + deployment isn't irrecoverably broken (e.g. due to misconfigured loadbalancing or + crash-looping workers) this is a transient failure and can be retried immediately. + + + + + The worker did not have the required permissions to make the request. Permissions do + not change at runtime, so it doesn't make sense to retry the request. + + + + + The command was delivered successfully, but the handler rejected it. Either the command + was delivered to a worker that explicitly rejected it by calling + Connection.SendCommandFailure, or the request data was rejected as invalid by SpatialOS + itself. In the latter case, in particular, Connection.SendCreateEntityRequest will + return ApplicationError if an entity ID reservation has expired, and + Connection.SendEntityQueryRequest will return ApplicationError if the result set is + incomplete. + + + + + Some other error occurred. This likely indicates a bug in SpatialOS and should be + reported. + + + + + The authority state of an entity-component. + + + + + Data for an operation that indicates the Connection has disconnected and can no longer be + used. + + + + + Data for an operation that indicates that a worker flag has been updated + + + + + Data for an operation that provides a log message from the SDK. + + + + + Data for an operation that provides a report on built-in metrics from the SDK. + + + + + Data for an operation that provides a report on built-in metrics from the SDK. + + + + + Data for an operation that indicates an entity has been added to the worker's view of the + simulation. + + + + + Data for an operation that indicates an entity has been removed from the worker's view of + the simulation. + + + + + A response indicating the result of the entity ID reservation request. + + + + + The outgoing request ID for which there was a response. Matches the request ID returned + by a previous call to Connection.SendReserveEntityIdRequest. + + + + The status code of the command response. + + + The error message. + + + + If successful, a newly allocated entity id which is guaranteed to be unused in the current + deployment. + + + + + A response indicating the result of the multiple entity ID reservation request. + + + + + The outgoing request ID for which there was a response. Matches the request ID returned + by a previous call to Connection.SendReserveEntityIdRequest. + + + + The status code of the command response. + + + The error message. + + + + If successful, an ID which is the first in a contiguous range of newly allocated entity + IDs which are guaranteed to be unused in the current deployment. + + + + + If successful, the number of IDs reserved in the contiguous range, otherwise 0. + + + + + A response indicating the result of an entity creation request. + + + + + The outgoing request ID for which there was a response. Matches the request ID returned + by a previous call to Connection.SendCreateEntityRequest. + + + + The status code of the command response. + + If the status code is StatusCode.ApplicationError, the entity ID reservation has + expired and must be retried. + + + + The error message. + + + If successful, the entity ID of the newly created entity. + + + + A response indicating the result of an entity deletion request. + + + + + The outgoing request ID for which there was a response. Matches the request ID returned + by a previous call to Connection.SendDeleteEntityRequest. + + + + The ID of the target entity of this request. + + + The status code of the command response. + + + The error message. + + + + A response indicating the result of an entity query request. + + + + + The outgoing request ID for which there was a response. Matches the request ID returned + by a previous call to Connection.SendEntityQueryRequest. + + + + The status code of the command response. + + + The error message. + + + The number of entities that matched the query. + + Note that a best-effort attempt is made to count the entities when the status code is + StatusCode.ApplicationError. In this case, the count can still be non-zero, but should + be considered a lower bound (i.e. there might be entities matching the query that were + not counted). + + + + The result of the query. Not used for CountResultType queries. + + Note that a best-effort attempt is made to get results when the status code is + StatusCode.ApplicationError. In this case, the result can still be non-empty, but should + be considered incomplete (i.e. there might be entities matching the query that were not + returned). + + + + + Data for an operation that indicates a component has been added to an existing entity in the + worker's view of the simulation. + + + + + Data for an operation that indicates a component has been removed from an existing entity in + the worker's view of the simulation. + + + + + Data for an operation that indicates the worker's authority over a component for an entity + has been changed. + + + + + Data for an operation that indicates the component for an entity has been updated. + + + + + Data for an operation that indicates a command request has been received for a component on an + entity over which this worker has authority. The worker should respond to the command by + calling Connection.SendCommandResponse with the given request ID. + + + + + The incoming request ID. Should be passed to Connection.SendCommandResponse in + order to respond to this request. + + + + The ID of the target entity of this request. + + + + An upper bound on the timeout of this request. Any response sent after the timeout has + expired will be ignored by the SDK. + + + + The ID of the worker that initiated this request. + + + The attribute set of the worker that initiated this request. + + + The request data. + + + + Data for an operation that indicates a command response has been received for a request + previously issued by this worker. The request ID will match a previous call to + Connection.SendCommandRequest. + + + + + The outgoing request ID for which there was a response. Matches the request ID returned + by a previous call to Connection.SendCommandRequest. + + + + The target entity ID of the original request. + + + The status code of the command response. + + + + A description of the status. Will contain the reason for failure if unsuccessful. + + + + + The command response data. Present exactly when the status code is StatusCode.Success. + + + + + A Dispatcher processes OpLists retrieved from the Connection and invokes appropriate + callbacks. + + + This object should not be modified concurrently by multiple threads. + + + + + + + + Registers a callback to be invoked when the Connection has disconnected and can no + longer be used. + + + + + Registers a callback to be invoked when a worker flag is changed + + + + + Registers a callback to be invoked when the SDK logs a message. + + + + + Registers a callback to be invoked when the SDK reports built-in metrics. + + + + + Registers a callback to be invoked when the message stream enters or leaves a critical + section. + + + + + Registers a callback to be invoked when an entity is added to the worker's view of the + simulation. + + + + + Registers a callback to be invoked when an entity is removed from the worker's view of + the simulation. + + + + + Registers a callback to be invoked when an entity ID reservation response is received. + + + + + Registers a callback to be invoked when a multiple entity ID reservation response is received. + + + + + Registers a callback to be invoked when an entity creation response is received. + + + + + Registers a callback to be invoked when an entity deletion response is received. + + + + + Registers a callback to be invoked when an entity query response is received. + + + + + Registers a callback to be invoked when a particular component is added to an existing + entity in the worker's view of the sumulation. + + + + + Registers a callback to be invoked when a particular component is removed from an + existing entity in the worker's view of the simulation. + + + + + Registers a callback to be invoked when the worker is granted authority over a + particular component for some entity, or when the worker's authority over that component + is revoked. + + + + + Registers a callback to be invoked when a particular component is updated for an entity. + + + + + Registers a callback to be invoked when a command request is received for a particular + component. + + + + + Registers a callback to be invoked when a command response is received for a particular + component. + + + + + Unregisters a callback identified by its CallbackKey, as returned from the registration + function. If the key does not exist, an exception will be thrown. + + + + + Processes an OpList and invokes registered callbacks. + + + + + Provides facilities for manipulating components in a type-safe way when the component type is + not known statically. + + + + + This interface should be implemented to provide component-specific behaviour + for the ForComponent and ForEachComponent methods. + + + + + Called by ForComponent and ForEachComponent, passing the + metaclass of a particular component. + + + + + Returns the component ID of a given component. + + + + + Returns the set of all known component IDs. + + + + + Invokes the Accept method on the provided handler with appropriate + arguments for the component whose ID matches the given component ID. + + + + + Invokes the Accept method on the provided handler with appropriate + arguments for every known component. + + + + + Stores the complete data for an entity's components. This is used both for representing the + initial set of components for an entity by the AddEntity operation, and inside the + (optional) View. + + + Note that an Entity object is simply a local data structure, and changes made here are not + automatically reflected across the SpatialOS simulation. To synchronize component state with + SpatialOS, use Connection.SendComponentUpdate. This object should not be modified + concurrently by multiple threads. + + + + + Retrieves data for the given component. Returns an empty option if the entity does not have the + given component. + + + + + Creates the given component with initial data. Has no effect if the entity already has + the given component. + + + + + Applies an update to the given component. Has no effect if the entity does not have the + given component. + + + + + Removes a component. + + + + + Returns the set of IDs of the components present in this entity. + + + + + A class representing the standard future concept. It can be used for both synchronous + and asynchronous interaction. + + The type of object the future returns. + + + + Objects of this class can be created by the SDK only. + + + + + + + + Waits until the result becomes available, and returns it. If the result was already + obtained by a previous call to Get() or Get(timeoutMillis), this function returns it + immediately. + + The result. + + + + Waits for the result to become available. Blocks until the specified timeout has + elapsed or the result has become available, whichever comes first. If the result was + already obtained by a previous call to Get() or Get(timeoutMillis), this function + returns it immediately. + + The time to wait for the result to become available. + The result if it is available; an empty option, otherwise. + + + + Details for a specific deployment obtained via Locator.GetDeploymentList. + + + + + The name of the deployment. + + + + + The name of the assembly used by this deployment. + + + + + Description of this deployment. + + + + + Number of users currently connected to the deployment. + + + + + Total user capacity of the deployment. + + + + + A deployment list obtained via Locator.GetDeploymentList. + + + + + List of accessible deployments for the given project. + + + + + Will be non-null if an error occurred. + + + + + A queue status update when connecting to a deployment via Locator.Connect. + + + + + Position in the queue. Decreases as we advance to the front of the queue. + + + + + Will be non-null if an error occurred. + + + + + A client which can be used to connect to a SpatialOS deployment via a locator service. + This is the standard flow used to connect a local worker to a cloud deployment. + + + This object should not be used concurrently by multiple threads. + + + + + Creates a client for the locator service. + + The hostname of the locator service. Typically either + "locator.improbable.io" (for production) or "locator-staging.improbable.io" + (for staging). + The parameters for the locator service. + + + + + + + Queries the current list of deployments for the project specified in the + LocatorParameters. The resulting future can be used to make this connection either + synchronously or asyncronously. + + A future object for the list of deployments. + + + + + Connects to a specific deployment. The resulting future can be used to make this + connection either synchronously or asyncronously. + + The deployment name, which should be obtained by calling + GetDeploymentListAsync. + The connection parameters. + The queueing callback, which should return false to cancel + queuing, or true to continue queueing. + A future object for the connection. + + + + + A histogram metric tracks observations of a given value by bucket. This corresponds to a + Prometheus histogram metric. + + This object should not be used concurrently by multiple threads. + + + + + A histogram bucket. + + + + + The upper bound. + + + + + The number of observations that were less than or equal to the upper bound. + + + + + Creates a histogram with the given bucket boundaries. Each bucket boundary is an upper + bound; the bucket tracks all observations with a value less than or equal to the bound. + A final bucket with a boundary of +INF is added automatically. + + + + + Creates a histogram with a single bucket. + + + + + Clears all recorded oservations. Automatically called by Connection.SendMetrics. + + + + + Records a sample and adds it to the corresponding buckets. + + + + + A set of metrics sent up from a worker to SpatialOS. + + + Keys for the contained metrics should match the following regex: + [a-zA-Z_][a-zA-Z0-9_]* + + + + + Copies all metrics from another Metrics object into this one, overwriting existing values. + + + + + The load value of this worker. A value of 0 indicates that the worker is completely + unloaded; a value greater than 1 indicates that the worker is overloaded. The load value + directs SpatialOS's load-balancing strategy for managed workers (spinning them up, + spinning them down, and assigning work between them). + + + + + Gauge metrics for the worker. + + + + + Histogram metrics for the worker. + + + + + Time (in milliseconds) that RakNet should use for its heartbeat protocol. + + + + + Number of multiplexed TCP connections. + + + + + Size in bytes of the TCP send buffer. + + + + + Size in bytes of the TCP receive buffer. + + + + + Whether to enable TCP_NODELAY. + + + + + Whether to connect to SpatialOS using the internal IP address. This is for managed + workers that run in the cloud alongside SpatialOS. + + + + + Type of network connection to use when connecting to SpatialOS. + + + + + Connection parameters specific to RakNet connections. + + + + + Connection parameters specific to TCP connections. + + + + + Log file names are prefixed with this prefix, are numbered, and have the extension .log. + + + + + Maximum number of log files to keep. Note that logs from any previous protocol logging + sessions will be overwritten. + + + + + Once the size of a log file reaches this size, a new log file is created. + + + + + Worker type (platform). + + + + + Parameters controlling the network connection to SpatialOS. + + + + + Number of messages that can be stored on the send queue. When the send queue is full, + calls to Connection.Send functions can block. + + + + + Number of messages that can be stored on the receive queue. When the receive queue is + full, SpatialOS can apply QoS and drop messages to the worker. + + + + + Number of messages logged by the SDK that can be stored in the log message queue. When + the log message queue is full, messages logged by the SDK can be dropped. + + + + + The Connection tracks several internal metrics, such as send and receive queue + statistics. This parameter controls how frequently the Connection will return a + MetricsOp reporting its built-in metrics. If set to zero, this functionality is + disabled. + + + + + Parameters for configuring protocol logging. + + + + + Whether to enable protocol logging at startup. + + + + + The token would typically be provided on the command-line by the SpatialOS + launcher. + + + + + Steam ticket for the steam app ID and publisher key corresponding to the project name + specified in the LocatorParameters. Typically obtained from the steam APIs. + + + + + Deployment tag to request access for. If non-empty, must match the following regex: + [A-Za-z0-9][A-Za-z0-9_]* + + + + + The name of the SpatialOS project. + + + + + Type of credentials to use when authenticating via the Locator. + + + + + Parameters used if the CredentialsType is LoginToken. + + + + + Parameters used if the CredentialsType is Steam. + + + + Base class for entity query constraints. + + + Constrains a query to match only entities with a particular ID. + + + Constrains a query to match only entities that have a specific component. + + + + Constrains a query to match only entities whose position lies within a given sphere. + + + + + Constrains a query by the conjunction of one or more constraints. + + + + + Constrains a query by the disjunction of one or more constraints. + + + + + Constrains a query by negating a constraint. + + + + Base class for entity query result types. + + + Indicates that a query should return the number of entites it matched. + + + + Indicates that a query should return a component data snapshot for each matched entity. + + + + + If nonempty, filters the components returned in the snapshot for each entity. + + + + Represents a global query for entity data across the simulation. + + + Type parameter for entity ID reservation request IDs. + + + Type parameter for multiple entity ID reservation request IDs. + + + Type parameter for entity creation request IDs. + + + Type parameter for entity deletion request IDs. + + + Type parameter for entity query request IDs. + + + Type parameter for outgoing entity command request IDs. + + + Type parameter for incoming entity command request IDs. + + + + Represents an ID for a request. The type parameter should be one of the marker interfaces + defined above. + + + + + The underlying raw ID of the request. Only use this if you know what you + are doing; prefer to use the RequestId object instead. + + + + + A stream for reading snapshot one Entity at a time. + + + + + Creates a SnapshotInputStream to read the Snapshot at the given string path. + + + + + Releases the resources of the SnapshotInputStream. + + + + + Reads the next EntityId entityId, Entity entity pair from the Snapshot. + + Returns an optional string error message if an error occurs during reading. + + + + + Returns true if the SnapshotInputStream has not reached the end of the Snapshot. + + + + + A stream for outputting entities to a Snapshot one at a time. + + + + + Creates a SnapshotOutputStream to write a Snapshot at the given string path. + + + + + Writes the end of Snapshot header and releases the resources of the SnapshotOutputStream. + + + + + Writes the EntityId entityId, Entity entity pair to the Snapshot. + + Returns an Optional string error message if an error occurs during writing. + + + + + The View is an optional data structure that maintains the known state of entities in the worker's + view of the simulation. + + + This object should not be modified concurrently by multiple threads. Note that as of + SpatialOS 11.1 this class is intended primarily as an example: using the + Improbable.Worker.Dynamic functionality, a custom View can be + implemented from scratch with any semantics desired. + + + + + Current component data for all entities in the worker's view. + + + + + Current authority delegations. + + + + + Helper function that checks if the worker has authority over a particular component of a + particular entity. + + + + + Merge toAdd and toRemove with map. + + + + + Registers that a call is entered to update the state of the map accordingly. + + + + + Registers that a call is exited to update the state of the map accordingly. + + + + + Checks that a disposableObject representing an unmanaged handle is not disposed + (assuming that in such a context it can no longer be used). If the handle has been + disposed of, this throws an ObjectDisposedException. Otherwise, nothing happens. + + + + + This attribute is valid on static functions and it is used by Mono's + Ahead of Time Compiler (AOT) to generate the code necessary to support + native calls back into managed code. + + + Implemented here as a custom attribute as we do not include Xamarin's + Mono library within the C# Worker SDK layer itself. Based on the official Mono + implementation. + See: https://github.com/mono/mono/blob/master/mcs/class/System/Mono.Util/MonoPInvokeCallbackAttribute.cs + See: https://developer.xamarin.com/api/type/MonoTouch.MonoPInvokeCallbackAttribute/ + + + + + Manages the associations between unique component IDs and instances of metaclasses. + + + + + Maps component IDs to their metaclass. + + + + + Looks up a metaclass instance from a component ID. + Returns null if the componentId is unknown. + + + + + Returns the component ID associated with a metaclass. + + + is not a known metaclass. + + + + diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.xml.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.xml.meta new file mode 100644 index 0000000..95061bf --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/Improbable.WorkerSdkCsharp.xml.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 246630db4b1384339bc2ea8c1003b825 +timeCreated: 1525947639 +licenseType: Pro +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/System.Threading.dll b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/System.Threading.dll new file mode 100644 index 0000000..0230d71 Binary files /dev/null and b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/System.Threading.dll differ diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/System.Threading.dll.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/System.Threading.dll.meta new file mode 100644 index 0000000..9c2e864 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Dll/System.Threading.dll.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: e46d97f5358a3cf438cb2c18a45d0c5d +timeCreated: 1521737937 +licenseType: Pro +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + isPreloaded: 0 + isOverridable: 0 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src.meta new file mode 100644 index 0000000..313f5ca --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 6211ca704866c7144885e9bc19eec81b +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor.meta new file mode 100644 index 0000000..b1eeb52 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 493074f38a060974db09660c701e59fc +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/.gitignore b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/.gitignore new file mode 100644 index 0000000..24d6d77 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/.gitignore @@ -0,0 +1,3 @@ +# Needed because we generally ignore folders named build. This makes git not ignore the directory. +!Build + diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Addons.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Addons.meta new file mode 100644 index 0000000..8f1742a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Addons.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 89c4660fa37cec541af037a0dfce3042 +folderAsset: yes +timeCreated: 1484301362 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Addons/SpatialCommand.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Addons/SpatialCommand.cs new file mode 100644 index 0000000..b29cd45 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Addons/SpatialCommand.cs @@ -0,0 +1,290 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Improbable.Unity.Editor.Core; +using Improbable.Unity.Util; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.Editor.Addons +{ + /// + /// Allow each user to specify the location of the spatial command. + /// + [InitializeOnLoad] + public sealed class SpatialCommand : ISpatialOsEditorAddon, ISpatialOsEditorAddonSettings + { + private static readonly SpatialCommand instance; + + internal const string usrLocalBin = "/usr/local/bin"; + + private string spatialLocation; + private string oldSpatialLocation; + private GUIContent buttonContent; + private GUIStyle iconStyle; + private string discoveredLocation; + internal Func> GetCommandLine { get; set; } + internal Func GetUserString { get; set; } + internal Func FileExists { get; set; } + internal Func GetEnvironmentVariable { get; set; } + + public const string SpatialPathArgument = "spatialCommandPath"; + + + /// + public string Name + { + get { return "Spatial CLI [Built-in]"; } + } + + /// + public string Vendor + { + get { return "Improbable Worlds, Ltd."; } + } + + /// + /// Returns the user-configured location of spatial[.exe], or simply "spatial" if it's not set. + /// + /// + /// If +spatialCommandPath "/path/to/spatial" is specified on the command line, it is used in preference to any user + /// settings. + /// By default, it is assumed that "spatial" will be on the system PATH. + /// + public static string SpatialPath + { + get { return instance.GetSpatialPath(); } + } + + /// + /// Starts a process and ensures that fullPathToSpatial is available in the PATH environment variable. + /// + public static Process RunCommandWithSpatialInThePath(string fullPathToSpatial, ProcessStartInfo startInfo) + { + return SpatialRunner.RunCommandWithSpatialInThePath(fullPathToSpatial, startInfo); + } + + /// + public void OnSettingsGui(Rect rect) + { + InitializeOnce(); + + using (new EditorGUILayout.VerticalScope()) + { + DrawSpatialLocationInput(); + + DrawDiscoveredLocation(); + + DrawUpdateButton(); + } + } + + private void DrawUpdateButton() + { + using (new EditorGUILayout.VerticalScope()) + { + EditorGUILayout.LabelField("Update Spatial CLI Version"); + using (new EditorGUI.DisabledScope(string.IsNullOrEmpty(discoveredLocation))) + { + if (GUILayout.Button(buttonContent)) + { + SpatialOsEditor.RunPausedProcess(SpatialPath, "update", ""); + } + } + } + } + + private void InitializeOnce() + { + if (spatialLocation == null) + { + spatialLocation = GetUserString(SpatialRunner.CommandLocationKey, string.Empty); + discoveredLocation = DiscoverSpatialLocationForDisplay(spatialLocation); + } + + if (buttonContent == null) + { + buttonContent = new GUIContent("Update") { tooltip = "Update spatial to the latest version" }; + } + + if (iconStyle == null) + { + iconStyle = new GUIStyle() { fixedWidth = 24, fixedHeight = 24 }; + } + } + + private void DrawSpatialLocationInput() + { + using (var check = new EditorGUI.ChangeCheckScope()) + using (new EditorGUILayout.VerticalScope()) + { + EditorGUILayout.PrefixLabel("Spatial CLI location"); + + oldSpatialLocation = spatialLocation; + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.HelpBox(spatialLocation, MessageType.None); + if (GUILayout.Button("...", GUILayout.ExpandWidth(false))) + { + BrowseForSpatial(); + } + + if (GUILayout.Button("Reset to default", GUILayout.ExpandWidth(false))) + { + spatialLocation = string.Empty; + } + } + + if (check.changed && oldSpatialLocation != spatialLocation) + { + CommitAndCheckSpatialLocation(); + } + } + } + + private void BrowseForSpatial() + { + var extension = string.Empty; + if (Application.platform == RuntimePlatform.WindowsEditor) + { + extension = "exe"; + } + + spatialLocation = EditorUtility.OpenFilePanel("Find spatial", spatialLocation, extension); + } + + private void CommitAndCheckSpatialLocation() + { + if (string.IsNullOrEmpty(spatialLocation)) + { + EditorPrefs.DeleteKey(SpatialRunner.CommandLocationKey); + } + else + { + EditorPrefs.SetString(SpatialRunner.CommandLocationKey, spatialLocation); + } + + discoveredLocation = DiscoverSpatialLocationForDisplay(spatialLocation); + } + + private void DrawDiscoveredLocation() + { + if (string.IsNullOrEmpty(discoveredLocation)) + { + EditorGUILayout.HelpBox("Could not find spatial.", MessageType.Error); + } + else + { + EditorGUILayout.HelpBox(string.Format("Found {0}.", discoveredLocation), MessageType.Info); + } + } + + private string DiscoverSpatialLocationForDisplay(string location) + { + bool viaPath; + var path = DiscoverSpatialLocation(location, out viaPath); + if (viaPath) + { + return path + " (via PATH)"; + } + else + { + return path; + } + } + + private string DiscoverSpatialLocation(string location) + { + bool viaPath; + return DiscoverSpatialLocation(location, out viaPath); + } + + private string DiscoverSpatialLocation(string location, out bool viaPath) + { + viaPath = false; + if (string.IsNullOrEmpty(location)) + { + var pathValue = GetEnvironmentVariable("PATH"); + if (pathValue == null) + { + return string.Empty; + } + + var fileName = SpatialRunner.DefaultSpatialCommand; + if (Application.platform == RuntimePlatform.WindowsEditor) + { + fileName = Path.ChangeExtension(fileName, ".exe"); + } + + var splitPath = pathValue.Split(Path.PathSeparator); + + if (Application.platform == RuntimePlatform.OSXEditor && !splitPath.Contains(usrLocalBin)) + { + splitPath = splitPath.Union(new[] { usrLocalBin }).ToArray(); + } + + foreach (var path in splitPath) + { + var testPath = Path.Combine(path, fileName); + if (FileExists(testPath)) + { + viaPath = true; + return testPath; + } + } + } + else + { + var fullLocation = location; + if (Application.platform == RuntimePlatform.WindowsEditor) + { + fullLocation = Path.ChangeExtension(fullLocation, ".exe"); + } + + if (FileExists(fullLocation)) + { + return fullLocation; + } + } + + return string.Empty; + } + + static SpatialCommand() + { + instance = new SpatialCommand(); + SpatialOsEditor.RegisterAddon(instance); + } + + internal SpatialCommand() + { + GetCommandLine = Environment.GetCommandLineArgs; + GetUserString = EditorPrefs.GetString; + FileExists = File.Exists; + GetEnvironmentVariable = Environment.GetEnvironmentVariable; + } + + internal string GetSpatialPath() + { + string path; + // The command line overrides everything. + if (!CommandLineUtil.TryGetCommandLineValue(GetCommandLine(), SpatialPathArgument, out path)) + { + // Then try the user-specific preferences + path = GetUserString(SpatialRunner.CommandLocationKey, string.Empty); + } + + // If nothing has been configured, assume it's on the system PATH, and use a sensible default of "spatial" + if (string.IsNullOrEmpty(path)) + { + path = DiscoverSpatialLocation(null); + } + + return path; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Addons/SpatialCommand.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Addons/SpatialCommand.cs.meta new file mode 100644 index 0000000..fff6154 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Addons/SpatialCommand.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8dd53f1d2784560428565c1ee01a2596 +timeCreated: 1484301362 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Assets.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Assets.meta new file mode 100644 index 0000000..54ce7f5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Assets.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 8be51be25a9e5c747a18ccdf1945b93a +folderAsset: yes +timeCreated: 1444833351 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Assets/EditorPrefabGameObjectLoader.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Assets/EditorPrefabGameObjectLoader.cs new file mode 100644 index 0000000..7268f40 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Assets/EditorPrefabGameObjectLoader.cs @@ -0,0 +1,93 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.IO; +using Improbable.Assets; +using Improbable.Unity.EditorTools.PrefabExport; +using Improbable.Unity.EditorTools.Util; +using Improbable.Unity.Util; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Assets +{ + public class EditorPrefabGameObjectLoader : IAssetLoader + { + private readonly IDictionary prefabs = new Dictionary(); + + public EditorPrefabGameObjectLoader() + { + CleanOutputFolder(); + FindPrefabsInProject(); + } + + public void LoadAsset(string prefabName, Action onGameObjectLoaded, Action onError) + { + try + { + string path; + if (prefabs.TryGetValue(prefabName, out path)) + { + var source = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)) as GameObject; + var prefabPath = Path.Combine(EditorPaths.PrefabCompileDirectory, prefabName + ".prefab"); + var prefabGameObject = PrefabUtility.CreatePrefab(prefabPath.ToUnityPath(), source); + if (prefabGameObject == null) + { + onError(new Exception(string.Format("Could not load the game object from the local prefab '{0}'.", prefabName))); + } + else + { + onGameObjectLoaded(prefabGameObject); + } + } + else + { + onError(new Exception(string.Format("Could not find the local prefab '{0}'.", prefabName))); + } + } + catch (Exception ex) + { + onError(ex); + } + } + + public void CancelAllLoads() + { + // LoadAsset is instantaneous, so no need to do anything here. + } + + private void FindPrefabsInProject() + { + var guids = EntityPrefabExporter.GetAllPrefabAssetGuids(); + foreach (var guid in guids) + { + var path = AssetDatabase.GUIDToAssetPath(guid); + var name = Path.GetFileNameWithoutExtension(path); + if (string.IsNullOrEmpty(name)) + { + Debug.LogWarningFormat("Found a prefab an empty name on path '{0}'. Please give it a name.", path); + } + else if (prefabs.ContainsKey(name)) + { + Debug.LogWarningFormat("Duplicate Prefab detected: {0}", path); + } + else + { + prefabs.Add(name, path); + } + } + } + + private static void CleanOutputFolder() + { + Directory.CreateDirectory(EditorPaths.PrefabCompileDirectory); + var info = new DirectoryInfo(EditorPaths.PrefabCompileDirectory); + var files = info.GetFiles(); + foreach (var fileInfo in files) + { + fileInfo.Delete(); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Assets/EditorPrefabGameObjectLoader.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Assets/EditorPrefabGameObjectLoader.cs.meta new file mode 100644 index 0000000..523eb63 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Assets/EditorPrefabGameObjectLoader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c0b626a98ab026e4d845fc8144d1286d +timeCreated: 1444833361 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build.meta new file mode 100644 index 0000000..a52f3ac --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: f0a3c1d2eaf553748a3ca862aee0a688 +folderAsset: yes +timeCreated: 1444833352 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/DefaultPlayerBuildConfiguration.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/DefaultPlayerBuildConfiguration.cs new file mode 100644 index 0000000..06ad55a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/DefaultPlayerBuildConfiguration.cs @@ -0,0 +1,62 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using System.IO; +using Improbable.Unity.Assets; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Build +{ + static class DefaultPlayerBuildConfiguration + { + private static readonly List CurrentPlatformTargetList = new List { "Current" }; + + internal static PlayerBuildConfiguation Generate() + { + var config = new PlayerBuildConfiguation + { + Deploy = new Enviroment + { + UnityWorker = new Config + { + Assets = AssetDatabaseStrategy.Streaming.ToString(), + Targets = new List + { + BuildTarget.StandaloneLinux64 + "?" + BuildOptions.EnableHeadlessMode + } + }, + UnityClient = new Config + { + Assets = AssetDatabaseStrategy.Streaming.ToString(), + Targets = new List + { + BuildTarget.StandaloneWindows.ToString(), +#if UNITY_2017_3_OR_NEWER + BuildTarget.StandaloneOSX.ToString() +#else + BuildTarget.StandaloneOSXIntel64.ToString() +#endif + } + } + }, + Develop = new Enviroment + { + UnityWorker = new Config + { + Assets = AssetDatabaseStrategy.Streaming.ToString(), + Targets = CurrentPlatformTargetList + }, + UnityClient = new Config + { + Assets = AssetDatabaseStrategy.Streaming.ToString(), + Targets = CurrentPlatformTargetList + }, + } + }; + var json = JsonUtility.ToJson(config, prettyPrint: true); + File.WriteAllText(UnityPlayerBuilders.PlayerConfigurationFilePath, json); + return config; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/DefaultPlayerBuildConfiguration.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/DefaultPlayerBuildConfiguration.cs.meta new file mode 100644 index 0000000..dc4f32f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/DefaultPlayerBuildConfiguration.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2fdf9a492d17e47bc888d1dae273b1ec +timeCreated: 1452782483 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPackager.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPackager.cs new file mode 100644 index 0000000..5bde22c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPackager.cs @@ -0,0 +1,19 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.EditorTools.Build +{ + [Obsolete("Obsolete in 10.3.0. Please see IPlayerBuildEvents for information about customizing player packaging.")] + public interface IPackager + { + /// the working directory that unity has built into. + /// + /// An IPackager takes a built Unity player and calls Prepare with the path where it's located. + /// It gives you the opportunity to copy over any extra files that the worker needs to be run, + /// before it is packaged, ready for consumption by SpatialOS. + /// To configure the IPackager in use, set the UnityPlayerBuilder.GetPackager function to return your custom packager. + /// + void Prepare(string packagePath); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPackager.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPackager.cs.meta new file mode 100644 index 0000000..0c0fc86 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPackager.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7550b0a77ccda5a408aa9ec917291a1e +timeCreated: 1444833359 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPlayerBuildEvents.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPlayerBuildEvents.cs new file mode 100644 index 0000000..598f41d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPlayerBuildEvents.cs @@ -0,0 +1,44 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEditor; + +namespace Improbable.Unity.EditorTools.Build +{ + /// + /// Inherit from this interface to provide custom behavior related to the SpatialOS player building process. + /// + public interface IPlayerBuildEvents + { + /// + /// Called before any players are built. + /// + void BeginBuild(); + + /// + /// Called after all players are built. + /// + /// + /// This will always be called, even if all players are not successfully built. + /// + void EndBuild(); + + /// + /// Called between and , for each worker type that is built. + /// Please reference for more information about how the returned array of + /// scenes is used by Unity's build process. + /// + /// The type of the worker being built. + /// An array of all scenes that should be included in the build. + string[] GetScenes(WorkerPlatform workerType); + + /// + /// Called between and , for each worker type that is packaged. + /// Implement this to modify the contents of the directory before packaging. + /// + /// The type of the worker being packaged. + /// The target platform for the player being packaged. + /// The configuration associated with the player being packaged. + /// The path that will be packaged. + void BeginPackage(WorkerPlatform workerType, BuildTarget target, Config config, string packagePath); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPlayerBuildEvents.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPlayerBuildEvents.cs.meta new file mode 100644 index 0000000..a327fc0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IPlayerBuildEvents.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c28a21ba747b50b4b8cd5b887bbbc788 +timeCreated: 1491492707 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IWorkerProvider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IWorkerProvider.cs new file mode 100644 index 0000000..c73f9be --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IWorkerProvider.cs @@ -0,0 +1,12 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using Improbable.Unity.Editor.Core; + +namespace Improbable.Unity.EditorTools.Build +{ + interface IWorkerProvider + { + IList GetWorkers(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IWorkerProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IWorkerProvider.cs.meta new file mode 100644 index 0000000..ee9f9ba --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/IWorkerProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: cbab9ebe0a02a4644a32c7ca474cafe5 +timeCreated: 1494259756 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerBuildConfiguation.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerBuildConfiguation.cs new file mode 100644 index 0000000..8f37a44 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerBuildConfiguation.cs @@ -0,0 +1,90 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor; + +namespace Improbable.Unity.EditorTools.Build +{ + /// + /// The class that represents the structure of the whole player-build-config.json file, i.e. + /// the configuration for building Unity players. + /// + [Serializable] + public class PlayerBuildConfiguation + { + // This field is always initialised with a new instance of + // GlobalConfig. By convention, we do not serialise it to + // the JSON output. + [NonSerialized] [Obsolete("Field Global is deprecated and will be removed in an upcoming SpatialOS version.")] + public GlobalConfig Global = new GlobalConfig(); + + public Enviroment Deploy; + public Enviroment Develop; + } + + /// + /// The global build configuration that applies to all built players. + /// + [Serializable] + [Obsolete("GlobalConfig is deprecated and will be removed in an upcoming SpatialOS version.")] + public class GlobalConfig + { + public PluginConfig Plugin = new PluginConfig(); + } + + /// + /// The global build configuration for Unity plugins. + /// + [Serializable] + [Obsolete("PluginConfig is deprecated and will be removed in an upcoming SpatialOS version.")] + public class PluginConfig + { + public bool UsePlatformDirectories = true; + } + + /// + /// The configuration for a particular build environment when building Unity players. + /// + [Serializable] + public class Enviroment + { + public Config UnityWorker; + public Config UnityClient; + } + + /// + /// The build configuration for a particular Unity player. + /// + [Serializable] + public class Config + { + private static readonly List NoBuildOptions = new List { BuildOptions.None }; + public List Targets = new Collections.List(); + + public string Assets; + + public IEnumerable FlagsForPlatform(string buildTarget) + { + return default(string) == buildTarget ? NoBuildOptions : ParseFlags(buildTarget); + } + + internal IEnumerable ParseFlags(string target) + { + var index = target.IndexOf('?'); + if (index == -1) + { + return NoBuildOptions; + } + + var flags = target.Substring(index + 1).Split(',').Select(s => s.Trim()).Select(ToBuildOptions); + return flags; + } + + private static BuildOptions ToBuildOptions(string value) + { + return (BuildOptions) Enum.Parse(typeof(BuildOptions), value); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerBuildConfiguation.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerBuildConfiguation.cs.meta new file mode 100644 index 0000000..a000807 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerBuildConfiguation.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 22d3acb0441e272488419b585114e63f +timeCreated: 1444833356 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerCompression.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerCompression.cs new file mode 100644 index 0000000..3f8fc24 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerCompression.cs @@ -0,0 +1,13 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.EditorTools.Build +{ + /// + /// Indicate whether or not built-out players should be compressed. + /// + public enum PlayerCompression + { + Enabled, + Disabled + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerCompression.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerCompression.cs.meta new file mode 100644 index 0000000..b5900ca --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/PlayerCompression.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7959fe6084e861a40ba7e1a06287096f +timeCreated: 1470317073 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/ReloadAssemblies.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/ReloadAssemblies.cs new file mode 100644 index 0000000..a114103 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/ReloadAssemblies.cs @@ -0,0 +1,106 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Improbable.Unity.Editor.Addons; +using Improbable.Unity.EditorTools.Util; +using UnityEditor; +using UnityEditor.Callbacks; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Build +{ + internal class ReloadAssemblies + { + private class CompareBuilders : IEqualityComparer + { + public bool Equals(UnityPlayerBuilder x, UnityPlayerBuilder y) + { + if (ReferenceEquals(x, y)) + { + return true; + } + + return x.AssemblyDirectory == y.AssemblyDirectory; + } + + public int GetHashCode(UnityPlayerBuilder obj) + { + return obj.AssemblyDirectory.GetHashCode(); + } + } + + [DidReloadScripts] + internal static void OnScriptsReloaded() + { + if (!UnityPlayerBuilderMenu.IsAutopatchEnabled()) + { + return; + } + + if (!Directory.Exists(EditorPaths.AssetDatabaseDirectory)) + { + return; + } + + if (EditorApplication.isPlayingOrWillChangePlaymode) + { + return; + } + + // Skip repackaging during command line builds, since users are explicitly building a set of players anyway. + if (Environment.GetCommandLineArgs().Select(s => s.ToLowerInvariant()).Contains("-batchmode")) + { + Debug.Log("Skipping auto-patching in batchmode"); + return; + } + + try + { + EditorApplication.LockReloadAssemblies(); + + var generatedCodeSourcePaths = Directory.GetFiles(EditorPaths.AssetDirectory, "*Generated.Code.dll", SearchOption.AllDirectories); + var scriptPaths = Directory.GetFiles(EditorPaths.ScriptAssembliesDirectory, "*Assembly-*.dll", SearchOption.AllDirectories).Where(p => !p.Contains("-Editor")); + + var allPaths = generatedCodeSourcePaths.Union(scriptPaths).Select(Path.GetFullPath).ToList(); + + var developmentPlayerBuilders = + UnityPlayerBuilders.DevelopmentPlayerBuilders(SimpleBuildSystem.GetWorkerTypesToBuild()); + + var deploymentPlayerBuilders = + UnityPlayerBuilders.DeploymentPlayerBuilders(SimpleBuildSystem.GetWorkerTypesToBuild()); + + var allPlayerBuilders = developmentPlayerBuilders + .Union(deploymentPlayerBuilders) + .Distinct(new CompareBuilders()).ToList(); + + var playerBuildEvents = SimpleBuildSystem.CreatePlayerBuildEventsAction(); + + foreach (var builder in allPlayerBuilders) + { + // No point in patching + packaging players that have not been built yet. + if (builder.AssemblyDirectoryEmpty()) + { + continue; + } + + Debug.LogFormat("Auto-patching {0} files in {1} {2}", allPaths.Count, builder.BuildTarget, builder.WorkerType); + + foreach (var sourcePath in allPaths) + { + builder.PatchAssembly(sourcePath); + } + + builder.PackagePlayer(playerBuildEvents, SpatialCommand.SpatialPath, PlayerCompression.Disabled); + } + } + finally + { + EditorApplication.UnlockReloadAssemblies(); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/ReloadAssemblies.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/ReloadAssemblies.cs.meta new file mode 100644 index 0000000..b016af9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/ReloadAssemblies.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 537a8b65886f5014e8d7554c34b17e79 +timeCreated: 1480680147 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimpleBuildSystem.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimpleBuildSystem.cs new file mode 100644 index 0000000..7afbc9b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimpleBuildSystem.cs @@ -0,0 +1,275 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Improbable.Editor.Configuration; +using Improbable.Unity.EditorTools.PrefabExport; +using Improbable.Unity.EditorTools.Util; +using Improbable.Unity.Util; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Build +{ + /// + /// This is a simple default build system that will compile assets and build workers. + /// + public static class SimpleBuildSystem + { + private static string target; + + /// + /// A list of all default types of players to build. + /// + public static readonly List AllWorkerTypes = new Collections.List + { + WorkerTypeUtils.UnityClientType, + WorkerTypeUtils.UnityWorkerType + }; + + /// + /// Override this to customize the actions that occur during "spatial worker build". + /// + public static Action BuildAction = DefaultBuild; + + /// + /// Override this to customize the actions that occur during "spatial worker clean". + /// + public static Action CleanAction = DefaultClean; + + /// + /// Override this to customize the actions that occur during "spatial worker codegen". + /// + public static Action CodegenAction = DefaultCodegen; + + /// + /// Override this to provide custom build events when building players. + /// This will be called once each time and + /// is called. + /// + public static Func CreatePlayerBuildEventsAction = DefaultCreatePlayerBuildEvents; + + /// + /// The name of the build target to run. If empty (the default) all targets will run. + /// Can be overridden with the IMPROBABLE_BUILD_TARGET environment variable. + /// + public static string Target + { + get + { + var envVar = Environment.GetEnvironmentVariable("IMPROBABLE_BUILD_TARGET"); + return string.IsNullOrEmpty(envVar) ? target : envVar; + } + set { target = value; } + } + + /// + /// A list of Worker names to build. If it is null or empty, then all available workers will be used. + /// + public static IList WorkersToBuild { get; set; } + + /// + /// The name(s) of the workers to build. Specify multiple targets by separating them with a comma. + /// For example: "UnityClient,UnityWorker". + /// + /// + /// Currently, the only possible values are "UnityWorker" and "UnityClient". + /// Defaults to AllWorkerTypes if the flag is not specified. + /// If commandLine is null, defaults to using Environment.GetCommandLineArgs();. + /// + public static IList GetWorkerTypesToBuild(string[] commandLine = null) + { + if (WorkersToBuild != null) + { + if (!WorkersToBuild.Any()) + { + return AllWorkerTypes; + } + + return WorkersToBuild; + } + + + if (commandLine == null) + { + commandLine = Environment.GetCommandLineArgs(); + } + + var commandLineValue = CommandLineUtil.GetCommandLineValue(commandLine, ConfigNames.BuildWorkerTypes, string.Empty); + if (string.IsNullOrEmpty(commandLineValue)) + { + return AllWorkerTypes; + } + + return ParseWorkerTypes(commandLineValue); + } + + /// + /// This is meant to be invoked by external build processes. + /// + public static void Build() + { + BuildAction(); + } + + /// + /// This is meant to be invoked by external build processes. + /// + public static void Clean() + { + CleanAction(); + } + + /// + /// Cleans player build directories and assemblies. + /// + public static void CleanPlayers() + { + Debug.LogFormat("Starting SpatialOS Unity Clean"); + + if (!string.IsNullOrEmpty(Target)) + { + Debug.LogFormat("Cleaning target '{0}', available targets are '{1}', '{2}'", Target, UnityPlayerBuilders.DeploymentTarget, UnityPlayerBuilders.DevelopmentTarget); + } + + var workersToClean = GetWorkerTypesToBuild(); + + foreach (var playerTarget in workersToClean) + { + Debug.LogFormat("Cleaning player {0}", playerTarget); + } + + RunIf(UnityPlayerBuilders.DevelopmentTarget, () => CleanPlayerAssemblies(UnityPlayerBuilders.DevelopmentPlayerBuilders(workersToClean))); + RunIf(UnityPlayerBuilders.DeploymentTarget, () => CleanPlayerAssemblies(UnityPlayerBuilders.DeploymentPlayerBuilders(workersToClean))); + + Debug.LogFormat("Finished SpatialOS Unity Clean"); + } + + /// + /// Cleans assemblies generated by the given player builders. + /// + private static void CleanPlayerAssemblies(IList playerBuilders) + { + foreach (var builder in playerBuilders) + { + builder.Clean(); + } + } + + /// + /// Performs default build steps for a Unity worker. + /// + private static void DefaultBuild() + { + Debug.LogFormat("Starting SpatialOS Build"); + + if (!string.IsNullOrEmpty(Target)) + { + Debug.LogFormat(@"Building target ""{0}"", available targets are ""{1}"", ""{2}""", Target, UnityPlayerBuilders.DeploymentTarget, UnityPlayerBuilders.DevelopmentTarget); + } + + EntityPrefabExportMenus.ExportAllEntityPrefabs(); + + var workersToBuild = GetWorkerTypesToBuild(); + + foreach (var playerTarget in workersToBuild) + { + Debug.LogFormat(@"Building player {0}", playerTarget); + } + + RunIf(UnityPlayerBuilders.DevelopmentTarget, () => UnityPlayerBuilders.BuildDevelopmentPlayers(workersToBuild)); + RunIf(UnityPlayerBuilders.DeploymentTarget, () => UnityPlayerBuilders.BuildDeploymentPlayers(workersToBuild)); + + Debug.LogFormat("Finished SpatialOS Build"); + } + + /// + /// Performs default clean steps for a Unity worker. + /// + private static void DefaultClean() + { + EntityPrefabExportMenus.CleanAllEntityPrefabs(); + + CleanPlayers(); + + CleanDeployedFramework(); + } + + /// + /// Performs default codegen steps for a Unity workers. + /// + private static void DefaultCodegen() + { + Debug.Log("Default codegen"); + } + + private static IPlayerBuildEvents DefaultCreatePlayerBuildEvents() + { + return new SingleScenePlayerBuildEvents(); + } + + /// + /// Removes all the elements of the framework that were deployed via spatial build. + /// + private static void CleanDeployedFramework() + { + var pluginDirectories = Directory.GetDirectories(EditorPaths.PluginDirectory, EditorPaths.OrganizationName, SearchOption.AllDirectories); + var directoriesToClean = new[] { EditorPaths.AssetDirectory }.Union(pluginDirectories); + + foreach (var directory in directoriesToClean) + { + try + { + UnityPathUtil.EnsureDirectoryRemoved(directory); + } + catch (Exception e) + { + Debug.LogError(e); + } + } + } + + private static void RunIf(string targetName, Action action) + { + if (string.IsNullOrEmpty(Target) || Target.ToLowerInvariant() == targetName) + { + Debug.LogFormat(@"Building target ""{0}"" ", targetName); + try + { + action(); + } + catch (Exception ex) + { + Debug.LogException(ex); + throw; + } + } + else + { + Debug.LogFormat("Skipping target '{0}', as it does not match target '{1}'", targetName, Target); + } + } + + private static IList ParseWorkerTypes(string targets) + { + var playerTargets = new HashSet(); + if (!string.IsNullOrEmpty(targets)) + { + var split = targets.Trim('\"').Split(','); + foreach (var str in split) + { + var trimmed = str.Trim(); + if (trimmed != WorkerTypeUtils.UnityClientType && trimmed != WorkerTypeUtils.UnityWorkerType) + { + throw new InvalidOperationException(string.Format("'{0}' is an unknown worker type. Accepted values are '{1}' and '{2}'", trimmed, WorkerTypeUtils.UnityClientType, WorkerTypeUtils.UnityWorkerType)); + } + + playerTargets.Add(trimmed); + } + } + + return playerTargets.ToList(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimpleBuildSystem.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimpleBuildSystem.cs.meta new file mode 100644 index 0000000..f06fa51 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimpleBuildSystem.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0b5e0dd06b65f154b87d53d25cb4e11f +timeCreated: 1469721780 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimplePackager.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimplePackager.cs new file mode 100644 index 0000000..8980898 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimplePackager.cs @@ -0,0 +1,14 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.EditorTools.Build +{ + [Obsolete("Obsolete in 10.3.0. Please see IPlayerBuildEvents for information about customizing player packaging.")] +#pragma warning disable 0618 + public class SimplePackager : IPackager +#pragma warning restore 0618 + { + public void Prepare(string packagePath) { } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimplePackager.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimplePackager.cs.meta new file mode 100644 index 0000000..41efe4e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SimplePackager.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 64d0dcc10cc45ae45b958aa2b9a8d318 +timeCreated: 1444833358 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SingleScenePlayerBuildEvents.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SingleScenePlayerBuildEvents.cs new file mode 100644 index 0000000..73731a4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SingleScenePlayerBuildEvents.cs @@ -0,0 +1,81 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEditor.SceneManagement; + +namespace Improbable.Unity.EditorTools.Build +{ + /// + /// Opens a single Unity scene per worker type. + /// + public class SingleScenePlayerBuildEvents : IPlayerBuildEvents + { + // During the build we will change the scene - remember so we can change it back. + private string savedScenePath; + + private Dictionary workerToScene = new Dictionary(); + + public SingleScenePlayerBuildEvents() + { + workerToScene[WorkerPlatform.UnityClient] = Path.Combine("Assets", "ClientScene.unity"); + workerToScene[WorkerPlatform.UnityWorker] = Path.Combine("Assets", "PhysicsServerScene.unity"); + } + + /// + /// Provides a mapping from the worker type to a path to a scene to load. + /// + public Dictionary WorkerToScene + { + get { return workerToScene; } + set { workerToScene = value; } + } + + /// + public virtual void BeginBuild() + { + savedScenePath = EditorSceneManager.GetActiveScene().path; + } + + /// + public virtual void EndBuild() + { + // Restore the previous scene. + if (!string.IsNullOrEmpty(savedScenePath)) + { + EditorSceneManager.OpenScene(savedScenePath, OpenSceneMode.Single); + } + } + + /// + public virtual string[] GetScenes(WorkerPlatform workerType) + { + var scenePath = workerToScene[workerType]; + var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); + ProcessScene(scene.name); + + return new[] { scenePath }; + } + + /// + public virtual void BeginPackage(WorkerPlatform workerType, BuildTarget target, Config config, string packagePath) + { +#pragma warning disable 0618 + UnityPlayerBuilder.GetPackager(workerType, target, config).Prepare(packagePath); +#pragma warning restore 0618 + } + + /// + /// Override to modify the scene before building. + /// + /// This is called just after the scene is loaded by . + /// The name of the scene to modify + public virtual void ProcessScene(string sceneName) + { +#pragma warning disable 0618 + UnityPlayerBuilder.ProcessScene(sceneName); +#pragma warning restore 0618 + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SingleScenePlayerBuildEvents.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SingleScenePlayerBuildEvents.cs.meta new file mode 100644 index 0000000..f4b4b9f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SingleScenePlayerBuildEvents.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6ccd6b6db6e790c439b39599451a6a8a +timeCreated: 1491492707 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SpatialZip.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SpatialZip.cs new file mode 100644 index 0000000..e6094d4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SpatialZip.cs @@ -0,0 +1,50 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Diagnostics; +using System.IO; +using Improbable.Unity.Editor; +using Improbable.Unity.Util; + +namespace Improbable.Unity.EditorTools.Build +{ + static class SpatialZip + { + private const string ZipSubCommand = "file zip"; + + internal static void Zip(string spatialCommand, string zipAbsolutePath, string basePath, string subFolder, string filePattern, PlayerCompression useCompression) + { + var zipFileFullPath = Path.GetFullPath(zipAbsolutePath); + var startInfo = new ProcessStartInfo + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + FileName = spatialCommand, + Arguments = ZipArgs(basePath, subFolder, filePattern, zipFileFullPath, useCompression), + CreateNoWindow = true + }; + + var zipProcess = SpatialRunner.RunCommandWithSpatialInThePath(spatialCommand, startInfo); + + var output = zipProcess.StandardOutput.ReadToEnd(); + var errOut = zipProcess.StandardError.ReadToEnd(); + zipProcess.WaitForExit(); + if (zipProcess.ExitCode != 0) + { + throw new Exception(string.Format("Could not package the folder {0}/{1}. The following error occurred: {2}, {3}\n", basePath, subFolder, output, errOut)); + } + } + + private static string ZipArgs(string basePath, string subFolder, string filePattern, string zipFileFullPath, PlayerCompression useCompression) + { + filePattern = string.IsNullOrEmpty(filePattern) ? "**" : filePattern; + return string.Format("{0} --output=\"{1}\" --basePath=\"{2}\" --relativePath=. \"{3}\" --compression={4}", + ZipSubCommand, + zipFileFullPath, + Path.GetFullPath(basePath), + PathUtil.EnsureTrailingSlash(subFolder) + filePattern, + useCompression == PlayerCompression.Enabled); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SpatialZip.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SpatialZip.cs.meta new file mode 100644 index 0000000..b7162d4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/SpatialZip.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: baea0b11d5f67f046bcb52129e81ca1e +timeCreated: 1464278801 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuildMenu.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuildMenu.cs new file mode 100644 index 0000000..6312cfb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuildMenu.cs @@ -0,0 +1,79 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Build +{ + [InitializeOnLoad] + public static class UnityPlayerBuilderMenu + { + private const string AutopatchMenuItem = "Improbable/Autopatch workers on editor reload"; + private const string AutoPatchEditorKey = "Improbable.Autopatch.Workers"; + + [MenuItem("Improbable/Build/Build deployment workers %#&3")] + public static void BuildDeploymentPlayers() + { + BuildPlayers(UnityPlayerBuilders.DeploymentTarget); + } + + [MenuItem("Improbable/Build/Build development workers %#&2")] + public static void BuildDevelopmentPlayers() + { + BuildPlayers(UnityPlayerBuilders.DevelopmentTarget); + } + + [MenuItem("Improbable/Build/Clean all workers %#&1")] + public static void CleanAllPlayers() + { + SimpleBuildSystem.CleanPlayers(); + } + + public static bool IsAutopatchEnabled() + { + return EditorPrefs.GetBool(AutoPatchEditorKey, defaultValue: false); + } + + [MenuItem(AutopatchMenuItem, true)] + public static bool ValidateAutoPatch() + { + // Ensure this is always up-to-date + Menu.SetChecked(AutopatchMenuItem, IsAutopatchEnabled()); + return true; + } + + [MenuItem(AutopatchMenuItem)] + public static void ToggleAutopatch() + { + var toggledValue = !IsAutopatchEnabled(); + EditorPrefs.SetBool(AutoPatchEditorKey, toggledValue); + + Debug.LogFormat("Auto-patching workers is {0}", toggledValue ? "enabled" : "disabled"); + + if (IsAutopatchEnabled()) + { + ReloadAssemblies.OnScriptsReloaded(); + } + } + + private static void BuildPlayers(string target) + { + var stopWatch = new System.Diagnostics.Stopwatch(); + stopWatch.Start(); + + try + { + SimpleBuildSystem.WorkersToBuild = SimpleBuildSystem.AllWorkerTypes; + SimpleBuildSystem.Target = target; + SimpleBuildSystem.Build(); + } + finally + { + SimpleBuildSystem.WorkersToBuild = null; + SimpleBuildSystem.Target = null; + + stopWatch.Stop(); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuildMenu.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuildMenu.cs.meta new file mode 100644 index 0000000..886f595 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuildMenu.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5925218dcb332be4696802bd391017b5 +timeCreated: 1444833358 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilder.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilder.cs new file mode 100644 index 0000000..494f60d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilder.cs @@ -0,0 +1,262 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Improbable.Unity.Assets; +using Improbable.Unity.EditorTools.Util; +using Improbable.Unity.Util; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Build +{ + public class UnityPlayerBuilder + { + /// This is obsolete, please see to customize player packaging. + [Obsolete("Obsolete in 10.3.0. Please see IPlayerBuildEvents for information about customizing player packaging.")] +#pragma warning disable 0618 + public static Func GetPackager = GetDefaultPackager; +#pragma warning restore 0618 + + /// This is obsolete, please see to customize scene processing. + [Obsolete("Obsolete in 10.3.0. Please see IPlayerBuildEvents for information about customizing scene loading.")] + public static Action ProcessScene = sceneName => { }; + + public readonly BuildTarget BuildTarget; + private readonly Config config; + public readonly WorkerPlatform WorkerType; + private readonly BuildOptions options; + private readonly PlatformData platformData; + + internal IPlayerBuildEvents PlayerBuildEvents { get; set; } + + public UnityPlayerBuilder(WorkerPlatform workerType, string targetString, Config config) + { + WorkerType = workerType; + BuildTarget = ToRuntimePlatform(targetString); + this.config = config; + options = GenerateFlag(config.FlagsForPlatform(targetString)); + platformData = CreatePlatformData(BuildTarget); + } + + private static BuildTarget ToRuntimePlatform(string platform) + { + if (platform.Contains("?")) + { + platform = platform.Substring(0, platform.IndexOf("?", StringComparison.Ordinal)); + } + + if (platform.ToLower() != "current") + { + var value = (BuildTarget) Enum.Parse(typeof(BuildTarget), platform); +#if UNITY_2017_3_OR_NEWER +#pragma warning disable 618 + if (value == BuildTarget.StandaloneOSXIntel64) + { + Debug.LogWarningFormat("{0} is deprecated and will be removed. Please update {1} to use {2} instead.", BuildTarget.StandaloneOSXIntel64, UnityPlayerBuilders.PlayerConfigurationFilePath, BuildTarget.StandaloneOSX); + value = BuildTarget.StandaloneOSX; + } +#pragma warning restore 618 +#endif + return value; + } + + return CurrentPlatform(); + } + + internal static BuildTarget CurrentPlatform() + { + switch (Application.platform) + { + case RuntimePlatform.WindowsEditor: + return BuildTarget.StandaloneWindows; + case RuntimePlatform.OSXEditor: +#if UNITY_2017_3_OR_NEWER + return BuildTarget.StandaloneOSX; +#else + return BuildTarget.StandaloneOSXIntel64; +#endif + case RuntimePlatform.LinuxEditor: + return BuildTarget.StandaloneLinux64; + default: + throw new System.ComponentModel.InvalidEnumArgumentException(string.Format("Unsupported runtime platform {0}", Application.platform)); + } + } + + public static string PlayerBuildScratchDirectory + { + get { return Path.GetFullPath(Path.Combine("build", "worker")); } + } + + public static string PlayerBuildDirectory + { + get { return PathUtil.Combine(Directory.GetCurrentDirectory(), EditorPaths.AssetDatabaseDirectory, "worker"); } + } + + private string ExecutableName + { + get + { + return string.Format("{0}@{1}{2}", WorkerType, platformData.BuildContext, + platformData.ExecutableExtension); + } + } + + private string PackageName + { + get { return string.Format("{0}@{1}", WorkerType, platformData.BuildContext); } + } + + private string PackagePath + { + get { return Path.Combine(PlayerBuildScratchDirectory, PackageName); } + } + + internal string AssemblyDirectory + { + get + { + switch (BuildTarget) + { +#if UNITY_2017_3_OR_NEWER + case BuildTarget.StandaloneOSX: +#else + case BuildTarget.StandaloneOSXIntel64: +#endif + { + return PathUtil.Combine(PackagePath, string.Format("{0}.app", PackageName), "Contents", "Resources", "Data", "Managed"); + } + default: + { + return PathUtil.Combine(PackagePath, string.Format("{0}_Data", PackageName), "Managed"); + } + } + } + } + + private string ZipPath + { + get { return Path.Combine(PlayerBuildDirectory, PackageName); } + } + + private string BuildConfigComment + { + get + { + return string.Format("WorkerType={0};BuildTarget={1};EmbedAssets={2};BuildOptions={3}", WorkerType, + BuildTarget, config.Assets == AssetDatabaseStrategy.Local.ToString(), options); + } + } + + public bool AssemblyDirectoryEmpty() + { + return !Directory.Exists(AssemblyDirectory) || !Directory.GetFileSystemEntries(AssemblyDirectory).Any(); + } + + public void PatchAssembly(string sourcePath) + { + if (!Directory.Exists(AssemblyDirectory)) + { + return; + } + + var fileName = Path.GetFileName(sourcePath); + var targetPath = Path.Combine(AssemblyDirectory, fileName); + + File.Copy(sourcePath, targetPath, overwrite: true); + } + +#pragma warning disable 0618 + [Obsolete("Obsolete in 10.3.0. Please see IPlayerBuildEvents for information about customizing player packaging.")] + public static IPackager GetDefaultPackager(WorkerPlatform workerType, BuildTarget buildTarget, Config config) +#pragma warning restore 0618 + { + return new SimplePackager(); + } + + public void Clean() + { + UnityPathUtil.EnsureDirectoryRemoved(PackagePath); + UnityPathUtil.EnsureFileRemoved(ZipPath + ".zip"); + } + + [Obsolete("Obsolete in 10.3.0. This will be removed in a future version.")] + public void PackagePlayer(string spatialPath, PlayerCompression useCompression) + { + PackagePlayer(PlayerBuildEvents, spatialPath, useCompression); + } + + internal void PackagePlayer(IPlayerBuildEvents events, string spatialPath, PlayerCompression useCompression) + { + events.BeginPackage(WorkerType, BuildTarget, config, PackagePath); + SpatialZip.Zip(spatialPath, ZipPath, PackagePath, ".", "**", useCompression); + } + + private static PlatformData CreatePlatformData(BuildTarget buildTarget) + { + switch (buildTarget) + { + case BuildTarget.StandaloneWindows: + return new PlatformData("Managed", "Windows", "_Data", ".exe"); + case BuildTarget.StandaloneWindows64: + return new PlatformData("Managed", "Windows", "_Data", ".exe"); +#if UNITY_2017_3_OR_NEWER + case BuildTarget.StandaloneOSX: +#else + case BuildTarget.StandaloneOSXIntel64: +#endif + return new PlatformData("Contents/Data/Managed", "Mac", ".app", ""); + case BuildTarget.StandaloneLinux64: + return new PlatformData("Managed", "Linux", "_Data", ""); + case BuildTarget.iOS: + return new PlatformData("Data/Managed", "iOS", "", ""); + } + + throw new ArgumentException("Unsupported platform " + buildTarget); + } + + internal void BuildPlayer() + { + PathUtil.EnsureDirectoryExists(PlayerBuildDirectory); + PathUtil.EnsureDirectoryExists(PlayerBuildScratchDirectory); + + var scenes = PlayerBuildEvents.GetScenes(WorkerType); + + var tempExecutablePath = Path.Combine(PackagePath, ExecutableName); + + var playerOptions = new BuildPlayerOptions { target = BuildTarget, locationPathName = tempExecutablePath, options = options, scenes = scenes }; + var buildErrorMessage = BuildPipeline.BuildPlayer(playerOptions); + if (!string.IsNullOrEmpty(buildErrorMessage)) + { + throw new ApplicationException(string.Format("Failed to build player {0} due to {1}", BuildConfigComment, + buildErrorMessage)); + } + + Debug.LogFormat("Built player {0} into {1}", BuildConfigComment, PackagePath); + } + + private BuildOptions GenerateFlag(IEnumerable flagList) + { + return flagList.Aggregate((a, b) => a | b); + } + } + + internal class PlatformData + { + public readonly string AssemblyPathWithinPackage; + public readonly string BuildContext; + public readonly string DataFolderExtension; + public readonly string ExecutableExtension; + + public PlatformData(string assemblyPathWithinPackage, string buildContext, string dataFolderExtension, + string executableExtension) + { + AssemblyPathWithinPackage = assemblyPathWithinPackage; + BuildContext = buildContext; + DataFolderExtension = dataFolderExtension; + ExecutableExtension = executableExtension; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilder.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilder.cs.meta new file mode 100644 index 0000000..42f9228 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 659e79647d9ba1f438631d78b45f717e +timeCreated: 1444833358 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilders.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilders.cs new file mode 100644 index 0000000..51dcd4c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilders.cs @@ -0,0 +1,183 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Improbable.Unity.Editor.Addons; +using Improbable.Unity.Editor.Core; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Build +{ + public static class UnityPlayerBuilders + { + public static readonly string PlayerConfigurationFilePath = Path.Combine(Application.dataPath, "player-build-config.json"); + + internal const string DeploymentTarget = "deployment"; + internal const string DevelopmentTarget = "development"; + + [Obsolete("GlobalConfig is deprecated and will be removed in an upcoming SpatialOS version.")] + public static GlobalConfig GlobalConfig + { + get { return LoadConfiguation().Global; } + } + + public static IList DeploymentPlayerBuilders(IList selectedWorkerTypes) + { + return ConfiguredBuilders(LoadConfiguation().Deploy, selectedWorkerTypes); + } + + public static IList DevelopmentPlayerBuilders(IList selectedWorkerTypes) + { + return ConfiguredBuilders(LoadConfiguation().Develop, selectedWorkerTypes); + } + + /// Retrieve plugin for current platform. + /// This is deprecated in favor of the spatialos_worker_packages.json + [Obsolete("RetrievePluginForCurrentPlatform is deprecated. Please use the spatialos_worker_packages.json to download the CoreSdkDll plugins.")] + public static void RetrievePluginForCurrentPlatform() { } + + /// Retrieves plugins for deployment players. + /// This is deprecated in favor of the spatialos_worker_packages.json + [Obsolete("RetrievePlayerPlugins is deprecated. Please use the spatialos_worker_packages.json to download the CoreSdkDll plugins.")] + public static void RetrievePlayerPlugins(IList buildTargets) { } + + public static void BuildDeploymentPlayers(IList selectedWorkerTypes) + { + BuildPlayers(DeploymentPlayerBuilders(selectedWorkerTypes), selectedWorkerTypes, PlayerCompression.Enabled); + } + + public static void BuildDevelopmentPlayers(IList selectedWorkerTypes) + { + BuildPlayers(DevelopmentPlayerBuilders(selectedWorkerTypes), selectedWorkerTypes, PlayerCompression.Disabled); + } + + private static void BuildPlayers(IList playerBuilders, IList selectedWorkerTypes, PlayerCompression compression) + { + PrepareWorkerAssembly(selectedWorkerTypes); + + var playerBuildEvents = SimpleBuildSystem.CreatePlayerBuildEventsAction(); + + var currentBuildTarget = EditorUserBuildSettings.activeBuildTarget; + try + { + EditorApplication.LockReloadAssemblies(); + playerBuildEvents.BeginBuild(); + + var exceptions = 0; + var threads = new List(); + + var spatialPath = SpatialCommand.SpatialPath; + + foreach (var playerBuilder in playerBuilders) + { + playerBuilder.PlayerBuildEvents = playerBuildEvents; + + playerBuilder.Clean(); + playerBuilder.BuildPlayer(); + var builder = playerBuilder; + + var thread = new Thread(() => + { + try + { +#pragma warning disable 0618 // Type or member is obsolete + builder.PackagePlayer(spatialPath, compression); +#pragma warning restore 0618 // Type or member is obsolete + } + catch (Exception e) + { + Debug.LogError(e); + Interlocked.Increment(ref exceptions); + throw; + } + }); + thread.Start(); + threads.Add(thread); + } + + try + { + for (var i = 0; i < threads.Count; ++i) + { + EditorUtility.DisplayProgressBar("Packaging players", "Packaging and zipping players. This may take a while.", (float) i / threads.Count); + threads[i].Join(); + } + } + finally + { + EditorUtility.ClearProgressBar(); + } + + if (exceptions > 0) + { + throw new Exception(string.Format("Building {0} of the players failed. Please look at logs.", exceptions)); + } + + Debug.Log("Finished building players."); + } + finally + { + EditorApplication.UnlockReloadAssemblies(); + playerBuildEvents.EndBuild(); +#pragma warning disable 618 + EditorUserBuildSettings.SwitchActiveBuildTarget(currentBuildTarget); +#pragma warning restore 618 + } + } + + private static IList ConfiguredBuilders(Enviroment env, IList selectedWorkerTypes) + { + var players = new List(); + + foreach (var t in selectedWorkerTypes) + { + switch (t) + { + case WorkerTypeUtils.UnityClientType: + players.AddRange(ToPlatformBuilders(WorkerPlatform.UnityClient, env.UnityClient)); + break; + case WorkerTypeUtils.UnityWorkerType: + players.AddRange(ToPlatformBuilders(WorkerPlatform.UnityWorker, env.UnityWorker)); + break; + default: + throw new InvalidOperationException(string.Format("Unknown player type '{0}'", t)); + } + } + + return players; + } + + private static IList ToPlatformBuilders(WorkerPlatform platform, Config config) + { + if (config == null) + { + return new List(); + } + + return config.Targets.Select(configTarget => new UnityPlayerBuilder(platform, configTarget, config)).ToList(); + } + + private static PlayerBuildConfiguation LoadConfiguation() + { + if (File.Exists(PlayerConfigurationFilePath)) + { + return JsonUtility.FromJson(File.ReadAllText(PlayerConfigurationFilePath)); + } + + return DefaultPlayerBuildConfiguration.Generate(); + } + + private static void PrepareWorkerAssembly(IList selectedWorkerTypes) + { + var workerTypeArguments = string.Join(" ", selectedWorkerTypes.Distinct().ToArray()); + var command = SpatialCommand.SpatialPath; + var arguments = "build build-config " + workerTypeArguments; + var applicationRootPath = Path.GetFullPath(Path.Combine(Application.dataPath, "../../..")); + SpatialOsEditor.RunProcess(command, arguments, applicationRootPath); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilders.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilders.cs.meta new file mode 100644 index 0000000..515bdb6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Build/UnityPlayerBuilders.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bedb4a8ce630e724e8fde8214892191f +timeCreated: 1444833361 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Configuration.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Configuration.meta new file mode 100644 index 0000000..217a240 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Configuration.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 5c4d6f53f89e4ac48a6d3a8fed508b06 +folderAsset: yes +timeCreated: 1479735828 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Configuration/ConfigNames.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Configuration/ConfigNames.cs new file mode 100644 index 0000000..9012cd0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Configuration/ConfigNames.cs @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +namespace Improbable.Editor.Configuration +{ + /// + /// Provides command line arguments to control editor-related operations. + /// + public static class ConfigNames + { + /// + /// The name(s) of the worker types that should be built. + /// + public const string BuildWorkerTypes = "buildWorkerTypes"; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Configuration/ConfigNames.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Configuration/ConfigNames.cs.meta new file mode 100644 index 0000000..0f8dae3 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Configuration/ConfigNames.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8dbb726e9f91408478a72b5e8d2f0b84 +timeCreated: 1479735828 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core.meta new file mode 100644 index 0000000..de865fd --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: e4a940bf280bee34085e2e104f6ea514 +folderAsset: yes +timeCreated: 1483974837 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/DefaultWorkerProvider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/DefaultWorkerProvider.cs new file mode 100644 index 0000000..6ea738b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/DefaultWorkerProvider.cs @@ -0,0 +1,57 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Improbable.Unity.Editor.Core; +using Improbable.Unity.EditorTools.Build; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Core +{ + /// + /// Provides workers from the worker.json files scanned from the worker's folder. + /// + internal class DefaultWorkerProvider : IWorkerProvider + { + private IList workers; + + private const string WorkerNamePrefix = "spatialos."; + private const string WorkerNameSuffix = ".worker.json"; + + public IList GetWorkers() + { + if (workers != null) + { + return workers; + } + + const string searchPattern = WorkerNamePrefix + "*" + WorkerNameSuffix; + try + { + var workerFiles = Directory.GetFiles(SpatialOsEditor.WorkerRootDir, searchPattern).Select(ExtractPlayerName); + workers = workerFiles.Select(f => new SpatialOsWorker(ExtractPlayerName(f))).ToList(); + return workers; + } + catch (Exception ex) + { + Debug.LogException(ex); + return new List(); + } + } + + private static string ExtractPlayerName(string fileName) + { + var workerName = Path.GetFileName(fileName); + if (workerName != null) + { + workerName = workerName.Replace(WorkerNamePrefix, string.Empty); + workerName = workerName.Replace(WorkerNameSuffix, string.Empty); + return workerName; + } + + return fileName; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/DefaultWorkerProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/DefaultWorkerProvider.cs.meta new file mode 100644 index 0000000..06b71d2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/DefaultWorkerProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bea1825da1427c14ba77dae72301967b +timeCreated: 1494259756 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddon.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddon.cs new file mode 100644 index 0000000..40f96c0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddon.cs @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Editor.Core +{ + /// + /// Implements a SpatialOS Addon for the Unity Editor. + /// + public interface ISpatialOsEditorAddon + { + /// + /// The name of the editor, as displayed to the user. + /// + string Name { get; } + + /// + /// The name of the vendor, as displayed to the user. + /// + string Vendor { get; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddon.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddon.cs.meta new file mode 100644 index 0000000..95b089f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddon.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d101de0eec61b824480aab20bc74bdb2 +timeCreated: 1484056898 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonBuild.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonBuild.cs new file mode 100644 index 0000000..3dd2bf8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonBuild.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Unity.Editor.Core +{ + /// + /// Allows an addon to present a user interface. + /// + public interface ISpatialOsEditorAddonBuild + { + /// + /// Called by the when the addon is visible. + /// + void OnDevGui(Rect rect); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonBuild.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonBuild.cs.meta new file mode 100644 index 0000000..399f6fa --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonBuild.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2554d6c3b0c1fbe4ca70f41a076b5466 +timeCreated: 1484299796 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonSettings.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonSettings.cs new file mode 100644 index 0000000..74f50f6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonSettings.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Unity.Editor.Core +{ + /// + /// Allows an addon to present an interface for modifying its settings. + /// + public interface ISpatialOsEditorAddonSettings + { + /// + /// Called by the when the addon needs to render settings. + /// + void OnSettingsGui(Rect rect); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonSettings.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonSettings.cs.meta new file mode 100644 index 0000000..8e3754a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonSettings.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 33798866714d7d8488be1c408334875b +timeCreated: 1484299796 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonToolbar.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonToolbar.cs new file mode 100644 index 0000000..cd9f7dc --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonToolbar.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Unity.Editor.Core +{ + /// + /// Allows an addon to present an interface in the development toolbar. + /// + public interface ISpatialOsEditorAddonToolbar + { + /// + /// Called by the when the addon needs to render to the toolbar. + /// + void OnToolbarGui(Rect rect); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonToolbar.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonToolbar.cs.meta new file mode 100644 index 0000000..4e20ec4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/ISpatialOsEditorAddonToolbar.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 89e03c9a26bb4624f904e835459de6f7 +timeCreated: 1484299797 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SharedGuiContent.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SharedGuiContent.cs new file mode 100644 index 0000000..acb08a8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SharedGuiContent.cs @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEditor; +using UnityEngine; + +namespace Improbable.Editor.Core +{ + /// + /// Gui-related content to provide a cohesive user experience. + /// + public class SharedGuiContent + { + /// + /// Draws a label that is sized to its content. + /// + public GUIStyle MinimalLabelStyle + { + get { return new GUIStyle(GUI.skin.label) { stretchWidth = false }; } + } + + /// + /// A foldout header with bold text. + /// + public GUIStyle BoldFoldout + { + get { return new GUIStyle(EditorStyles.foldout) { fontStyle = FontStyle.Bold }; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SharedGuiContent.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SharedGuiContent.cs.meta new file mode 100644 index 0000000..8447797 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SharedGuiContent.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ebfa2a05eda227a4084af6ad872551f6 +timeCreated: 1484056898 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsAuxWindow.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsAuxWindow.cs new file mode 100644 index 0000000..9892d4e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsAuxWindow.cs @@ -0,0 +1,168 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Linq; +using Improbable.Unity.EditorTools; +using Improbable.Unity.EditorTools.Util; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.Editor.Core +{ + [Serializable] + class SpatialOsAuxWindow : EditorWindow + { + [SerializeField] private string addonTitle; + [SerializeField] private string typeName; + [SerializeField] private AddonUiStateDictinoary addonState; + + private ISpatialOsEditorAddon[] addons; + private Type addonType; + + SpatialOsAuxWindow() + { + titleContent = new GUIContent("SpatialOS"); + } + + public void OnEnable() + { + if (addonState == null) + { + addonState = new AddonUiStateDictinoary(); + } + + if (typeName != null) + { + var type = Type.GetType(typeName, false); + if (type != null) + { + SetAddonType(type, addonTitle); + } + else + { + Debug.LogErrorFormat("Could not find type {0}", typeName); + typeName = null; + addons = null; + } + + Repaint(); + } + } + + public void SetAddonType(Type type, string newAddonTitle) + { + addonType = type; + addons = SpatialOsEditor.Addons().Where(type.IsInstanceOfType).ToArray(); + typeName = type.FullName; + addonTitle = newAddonTitle; + titleContent = new GUIContent(newAddonTitle); + Repaint(); + } + + public void OnGUI() + { + if (SpatialOsWindow.SharedContent == null) + { + return; + } + + if (addons == null || addons.Length == 0) + { + using (new EditorGUILayout.VerticalScope()) + { + EditorGUILayout.HelpBox("Please select an item in the SpatialOS window.", MessageType.Info); + if (GUILayout.Button("Show SpatialOS window")) + { + GetWindow().Show(); + } + } + + return; + } + + AddonUiState s; + if (!addonState.TryGetValue(typeName, out s)) + { + addonState[typeName] = s = new AddonUiState(); + } + + if (s.Selected == null || s.Selected.Length != addons.Length) + { + s.Selected = Enumerable.Repeat(true, addons.Length).ToArray(); + } + + if (s.Rects == null || s.Rects.Length != addons.Length) + { + s.Rects = new Rect[addons.Length]; + } + + var rect = new Rect(0, 0, position.width, position.height); + + using (var scroller = new EditorGUILayout.ScrollViewScope(s.ScrollPosition, false, false)) + using (new EditorGUILayout.VerticalScope()) + { + for (var index = 0; index < addons.Length; index++) + { + using (new EditorGUILayout.VerticalScope()) + { + EditorGUILayout.Separator(); + s.Selected[index] = EditorGUILayout.Foldout(s.Selected[index], addons[index].Name, + SpatialOsWindow.SharedContent.BoldFoldout); + if (s.Selected[index]) + { + if (addonType.IsAssignableFrom(typeof(ISpatialOsEditorAddonBuild))) + { + ((ISpatialOsEditorAddonBuild) addons[index]).OnDevGui(rect); + } + else if (addonType.IsAssignableFrom(typeof(ISpatialOsEditorAddonSettings))) + { + ((ISpatialOsEditorAddonSettings) addons[index]).OnSettingsGui(rect); + } + else + { + EditorGUILayout.HelpBox( + string.Format("Can't draw addons of type {0}", addons[index].GetType()), + MessageType.Warning); + } + } + + EditorGUILayout.Separator(); + } + + if (Event.current.type == EventType.Repaint) + { + s.Rects[index] = GUILayoutUtility.GetLastRect(); + } + } + + s.ScrollPosition = scroller.scrollPosition; + } + + + Handles.BeginGUI(); + Handles.color = new Color(0.5f, 0.5f, 0.5f, 1f); + + for (var index = 1; index < addons.Length; index++) + { + var yMin = s.Rects[index].yMin - s.ScrollPosition.y; + Handles.DrawLine(new Vector3(0, yMin), new Vector3(s.Rects[index].xMax, yMin)); + } + + Handles.EndGUI(); + } + + [Serializable] + private class AddonUiState + { + public bool[] Selected; + public Vector2 ScrollPosition; + public Rect[] Rects; + } + + /// + /// Unity can't serialize generics directly. + /// + [Serializable] + private class AddonUiStateDictinoary : SerializableDictionary { } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsAuxWindow.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsAuxWindow.cs.meta new file mode 100644 index 0000000..d7f45de --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsAuxWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 55b72e45cb1ddf54eb61db17215a3bea +timeCreated: 1494404803 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsEditor.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsEditor.cs new file mode 100644 index 0000000..ce58309 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsEditor.cs @@ -0,0 +1,223 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Improbable.Unity.Core; +using Improbable.Unity.EditorTools.Build; +using Improbable.Unity.EditorTools.Core; +using Improbable.Unity.Util; +using UnityEditor; +using UnityEngine; +using Debug = UnityEngine.Debug; + +namespace Improbable.Unity.Editor.Core +{ + /// + /// Provides SpatialOS-specific functionality for the Unity Editor. + /// + [InitializeOnLoad] + public static class SpatialOsEditor + { + private static readonly Dictionary RegisteredAddons = new Dictionary(); + + private static ProjectDescriptor projectDescriptor; + + private static IWorkerProvider workerProvider; + private static WorkerSelection workerSelection; + + private static readonly string WorkerSelectionAssetPath; + + /// + /// The absolute path of the worker. + /// + public static string WorkerRootDir { get; private set; } + + /// + /// The absolute path of the SpatialOS project. + /// + public static string ApplicationRootDir { get; private set; } + + /// + /// Contains information about the SpatialOS project as a whole. + /// + public static ProjectDescriptor ProjectDescriptor + { + get { return projectDescriptor ?? (projectDescriptor = ProjectDescriptor.Load()); } + } + + internal static IWorkerProvider WorkerProvider + { + get { return workerProvider ?? (workerProvider = new DefaultWorkerProvider()); } + set { workerProvider = value; } + } + + /// + /// Manages the selection of available workers. + /// + public static WorkerSelection WorkerSelection + { + get + { + if (workerSelection != null) + { + return workerSelection; + } + + workerSelection = new WorkerSelection(); + InitializeWorkerSelection(workerSelection); + return workerSelection; + } + } + + /// + /// Gets an instance of a specific addon. + /// + /// If the addon is not registered. + /// > + public static TAddon GetAddon() where TAddon : ISpatialOsEditorAddon + { + return (TAddon) RegisteredAddons[typeof(TAddon)]; + } + + /// + /// Registers an instance + /// + /// If the addon is already registered. + public static void RegisterAddon(ISpatialOsEditorAddon addon) + { + if (RegisteredAddons.ContainsKey(addon.GetType())) + { + throw new InvalidOperationException(string.Format("{0} is already registered", addon.GetType())); + } + + RegisteredAddons[addon.GetType()] = addon; + } + + /// + /// Returns a list of all currently-registered addons. + /// + public static IList Addons() + { + return RegisteredAddons.Select(kv => kv.Value).ToList(); + } + + /// + /// Workers that exist within the current Unity project. + /// + public static IList Workers + { + get { return WorkerProvider.GetWorkers(); } + } + + /// + /// Temporary solution for running external processes. + /// + public static void RunProcess(string command, string arguments, string workingDir) + { + var process = new Process + { + StartInfo = + { + FileName = command, + Arguments = arguments, + WorkingDirectory = workingDir, + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + } + }; + + try + { + process.Start(); + process.WaitForExit(); + + var output = process.StandardOutput.ReadToEnd(); + var error = process.StandardError.ReadToEnd(); + + // This is a workaround as toolbelt's regular output gets piped to stderror for some reason. + LogAsSeparateLines(output + "\n" + error, (process.ExitCode != 0) ? LogType.Error : LogType.Log); + if (process.ExitCode != 0) + { + throw new Exception(string.Format("Process call {0} {1} failed with exit code {2}.", command, arguments, process.ExitCode)); + } + } + finally + { + process.Close(); + } + } + + private static void LogAsSeparateLines(string input, LogType logType) + { + var lines = input.Split('\n').Select(s => s.Trim()).Where(line => !string.IsNullOrEmpty(line)); + foreach (var line in lines) + { +#pragma warning disable 618 + Debug.logger.LogFormat(logType, "{0}", line); +#pragma warning restore 618 + } + } + + /// + /// Temporary solution for running external processes. + /// + public static void RunPausedProcess(string command, string arguments, string workingDir) + { + SpatialRunner.RunPausedProcess(command, arguments, workingDir); + } + + static SpatialOsEditor() + { + WorkerRootDir = Path.GetFullPath(PathUtil.Combine(Application.dataPath, "..")); + ApplicationRootDir = Path.GetFullPath(PathUtil.Combine(WorkerRootDir, "..", "..")); + WorkerSelectionAssetPath = PathUtil.Combine(WorkerRootDir, "build", "selected_workers.txt"); + } + + /// + /// Read the names of workers from a text file, one worker name per line. Empty lines are ignored. + /// + /// + /// This exists because we want to store these per-unity project, so the filesystem is the best place. + /// EditorPrefs is global to all Unity instances. + /// + private static void InitializeWorkerSelection(WorkerSelection selection) + { + // Default to selecting all workers. + IEnumerable toSelect = Workers; + + // Load from disk and reconcile the previous selection against available workers. + if (File.Exists(WorkerSelectionAssetPath)) + { + try + { + var sourceText = File.ReadAllText(WorkerSelectionAssetPath); + var previouslySelected = sourceText.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); + toSelect = Workers.Where(w => previouslySelected.Contains(w.Name)); + } + catch { } + } + + // Restore the selection state. + foreach (var worker in toSelect) + { + selection.SelectWorker(worker, true); + } + + // Listen for selections events so we can save the current selection to disk. + selection.OnWorkerSelectionChanged += worker => + { + try + { + var text = string.Join("\n", workerSelection.SelectedWorkers.Select(w => w.Name).ToArray()); + File.WriteAllText(WorkerSelectionAssetPath, text); + } + catch { } + }; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsEditor.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsEditor.cs.meta new file mode 100644 index 0000000..f230f37 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsEditor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 42eef98b867d6204488acd2ed350b7fa +timeCreated: 1481555891 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWindow.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWindow.cs new file mode 100644 index 0000000..3a6e21e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWindow.cs @@ -0,0 +1,154 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Text.RegularExpressions; +using Improbable.Editor.Core; +using Improbable.Unity.Editor.Core; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.EditorTools +{ + /// + /// Provides a user interface to common SpatialOS-related development commands. + /// + [Serializable] + [InitializeOnLoad] + public class SpatialOsWindow : EditorWindow + { + private const string MinVersion = "5.6.0"; + private const string MaxVersion = "2017.3.0"; + + private static SharedGuiContent sharedContent; + private static string versionMessage; + + private GUIStyle boldStyle; + private GUIStyle selectionGridStyle; + + private GUIStyle feedbackButtonStyle; + + private Rect supportButtonRect; + + [MenuItem("Window/SpatialOS")] + [MenuItem("Improbable/SpatialOS Window")] + private static void ShowWindow() + { + var instance = GetWindow(); + instance.titleContent = new GUIContent("SpatialOS"); + instance.minSize = new Vector2(320, 24); + } + + /// + /// Accesses common GUI system content and helpers. + /// + public static SharedGuiContent SharedContent + { + get { return sharedContent ?? (sharedContent = new SharedGuiContent()); } + } + + public void OnEnable() + { + try + { + long version = ConvertUnityVersionToNumber(Application.unityVersion); + long min = ConvertUnityVersionToNumber(MinVersion); + long max = ConvertUnityVersionToNumber(MaxVersion); + + if (version < min) + { + versionMessage = string.Format("Your Unity version: {0}\nEarliest tested: {1}.\nPlease use at least {1} for the best experience.", Application.unityVersion, MinVersion); + } + else if (version > max) + { + versionMessage = string.Format("Your Unity version: {0}\nLatest tested: {1}.\nThings should work fine, but you may find issues.", Application.unityVersion, MaxVersion); + } + } + catch (Exception) + { + versionMessage = string.Format("Unity {0} may be untested with the SpatialOS for Unity SDK.", Application.unityVersion); + } + + if (!string.IsNullOrEmpty(versionMessage)) + { + EditorWindow.GetWindow().minSize = new Vector2(320, 72); + } + } + + public void OnDestroy() { } + + public void OnGUI() + { + InitializeOnce(); + + using (new EditorGUILayout.VerticalScope()) + { + if (!string.IsNullOrEmpty(versionMessage)) + { + EditorGUILayout.HelpBox(versionMessage, MessageType.Warning); + } + + using (new EditorGUILayout.HorizontalScope()) + { + using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false))) + { + EditorGUILayout.SelectableLabel(SpatialOsEditor.ProjectDescriptor.SdkVersion, SharedContent.MinimalLabelStyle); + EditorGUILayout.SelectableLabel(SpatialOsEditor.ProjectDescriptor.Name, SharedContent.MinimalLabelStyle); + } + + if (GUILayout.Button("Build")) + { + EditorWindow.GetWindow().SetAddonType(typeof(ISpatialOsEditorAddonBuild), "Build"); + } + + if (GUILayout.Button("Settings")) + { + EditorWindow.GetWindow().SetAddonType(typeof(ISpatialOsEditorAddonSettings), "Settings"); + } + + if (GUILayout.Button("Support")) + { + PopupWindow.Show(supportButtonRect, new SupportPopup()); + } + + if (Event.current.type == EventType.Repaint) + { + supportButtonRect = GUILayoutUtility.GetLastRect(); + } + } + } + } + + private void InitializeOnce() + { + if (boldStyle == null) + { + boldStyle = new GUIStyle(GUI.skin.label) { fontStyle = FontStyle.Bold }; + } + + if (selectionGridStyle == null) + { + selectionGridStyle = new GUIStyle(GUI.skin.button) { fixedHeight = 48, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(4, 4, 4, 4) }; + } + + if (feedbackButtonStyle == null) + { + feedbackButtonStyle = new GUIStyle(GUI.skin.button) { fixedHeight = 32, fixedWidth = 32, imagePosition = ImagePosition.ImageLeft }; + } + } + + private static long ConvertUnityVersionToNumber(string version) + { + var matches = Regex.Matches(version, @"(\d+)\.(\d+)\.(\d+).*"); + + if (matches.Count < 1) + { + throw new InvalidOperationException(); + } + + var groups = matches[0].Groups; + + // groups[0] is the whole string that matches; the following elements are the captured matches. + return long.Parse(groups[1].Value) * 100 + long.Parse(groups[2].Value) * 10 + long.Parse(groups[3].Value); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWindow.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWindow.cs.meta new file mode 100644 index 0000000..d269784 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWindow.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 96748f9e4de7719419aea56075a8a14a +timeCreated: 1515171104 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWorker.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWorker.cs new file mode 100644 index 0000000..f666b49 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWorker.cs @@ -0,0 +1,62 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.Editor.Core +{ + /// + /// Represents a worker in the current project. + /// + public class SpatialOsWorker : IEquatable + { + /// + /// The name of the worker. + /// + public string Name { get; private set; } + + internal SpatialOsWorker(string name) + { + Name = name; + } + + public bool Equals(SpatialOsWorker other) + { + if (ReferenceEquals(null, other)) + { + return false; + } + + if (ReferenceEquals(this, other)) + { + return true; + } + + return string.Equals(Name, other.Name); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((SpatialOsWorker) obj); + } + + public override int GetHashCode() + { + return (Name != null ? Name.GetHashCode() : 0); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWorker.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWorker.cs.meta new file mode 100644 index 0000000..7ae4856 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialOsWorker.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 15592119097408b4787af410c744110c +timeCreated: 1484056898 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialRunner.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialRunner.cs new file mode 100644 index 0000000..3ee72ef --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialRunner.cs @@ -0,0 +1,120 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.Editor +{ + /// + /// Helper to allow for running the spatial CLI tool. + /// + static class SpatialRunner + { + // Ensure that a new script file is written during a particular session. + private static int processRunCounter; + + private const string SpatialCommandLaunchFailure = + "Could not launch spatial. Make sure it was on the PATH when Unity launched, " + + "the Unity process has permissions to execute it, " + + "and the binary is not corrupted (e.g. by running spatial update).\n" + + "Error Code 0x{0:X8}\n{1}\n"; + + public const string DefaultSpatialCommand = "spatial"; + + public const string CommandLocationKey = "Improbable.Unity.Editor.Addons.SpatialCommand.Location"; + + /// + /// Temporary solution for running external processes. + /// + public static void RunPausedProcess(string command, string arguments, string workingDir) + { + string scriptPath = Path.Combine(Path.GetTempPath(), string.Format("unitysdk_spatialos_command_{0}", processRunCounter++)); + string scriptCommand; + string scriptArguments; + + if (Application.platform == RuntimePlatform.WindowsEditor) + { + scriptCommand = "cmd"; + scriptPath += ".cmd"; + scriptArguments = string.Format("/c \"{0}\"", scriptPath); + + File.WriteAllText(scriptPath, string.Format("@{0} {1}\r\n@pause\r\n@del \"{2}\"", command, arguments, scriptPath)); + } + else + { + scriptCommand = "open"; + scriptPath += ".command"; + scriptArguments = string.Format("\"{0}\"", scriptPath); + File.WriteAllText(scriptPath, string.Format("cd \"{0}\"\n{1} {2}\nread -n 1 -s -p \"Press any key to continue\"\nrm \"{3}\"", workingDir, command, arguments, scriptPath)); + + MakeScriptAsExecutable(scriptPath); + } + + var startInfo = new ProcessStartInfo(scriptCommand, scriptArguments) + { + WorkingDirectory = workingDir, + UseShellExecute = false, + CreateNoWindow = false, + WindowStyle = ProcessWindowStyle.Normal + }; + + // Try to use what the user has setup before. + var fullPathToSpatial = EditorPrefs.GetString(CommandLocationKey, DefaultSpatialCommand); + + var process = RunCommandWithSpatialInThePath(fullPathToSpatial, startInfo); + if (process != null) + { + process.Close(); + } + } + + private static void MakeScriptAsExecutable(string scriptPath) + { + var chmodStartInfo = new ProcessStartInfo("chmod", string.Format("+x \"{0}\"", scriptPath)) + { + UseShellExecute = false, + CreateNoWindow = true + }; + + using (var chmodProcess = Process.Start(chmodStartInfo)) + { + if (chmodProcess != null) + { + chmodProcess.WaitForExit(); + } + } + } + + public static Process RunCommandWithSpatialInThePath(string fullPathToSpatial, ProcessStartInfo startInfo) + { + var spatialLocation = Path.GetDirectoryName(fullPathToSpatial); + + if (!string.IsNullOrEmpty(spatialLocation)) + { + var newPathEnv = Environment.GetEnvironmentVariable("PATH"); + newPathEnv = string.Format("{0}{1}{2}", newPathEnv, Path.PathSeparator, spatialLocation); + startInfo.EnvironmentVariables["PATH"] = newPathEnv; + } + + // Ensure UNITY_HOME reflects the running instance of Unity. + // EditorApplication.applicationContentsPath returns "/Editor/Data", so strip the upper two directories. + var combined = Path.Combine(EditorApplication.applicationContentsPath, ".."); + var applicationPath = Path.GetFullPath(Path.Combine(combined, "..")); + startInfo.EnvironmentVariables["UNITY_HOME"] = applicationPath; + + try + { + return Process.Start(startInfo); + } + catch (Win32Exception e) + { + var errorMessage = string.Format(SpatialCommandLaunchFailure, e.ErrorCode, e.Message); + throw new Exception(errorMessage); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialRunner.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialRunner.cs.meta new file mode 100644 index 0000000..4bcbd8a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SpatialRunner.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 19fc60b5719e31c44a6412ff8ad5d8f4 +timeCreated: 1515410418 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SupportPopup.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SupportPopup.cs new file mode 100644 index 0000000..26ce46a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SupportPopup.cs @@ -0,0 +1,37 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Diagnostics; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.EditorTools +{ + internal class SupportPopup : PopupWindowContent + { + public override Vector2 GetWindowSize() + { + return new Vector2(120, 68); + } + + public override void OnGUI(Rect rect) + { + using (new EditorGUILayout.VerticalScope()) + { + if (GUILayout.Button("Forums")) + { + Process.Start("https://forums.improbable.io"); + } + + if (GUILayout.Button("Documentation")) + { + Process.Start("https://spatialos.improbable.io/docs"); + } + + if (GUILayout.Button("Send feedback")) + { + Process.Start("https://forums.improbable.io/c/fb"); + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SupportPopup.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SupportPopup.cs.meta new file mode 100644 index 0000000..e4b81df --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/SupportPopup.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9896d06ab57edb1449e66dd4997c68cb +timeCreated: 1494404803 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/WorkerSelection.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/WorkerSelection.cs new file mode 100644 index 0000000..52e2c31 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/WorkerSelection.cs @@ -0,0 +1,57 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; + +namespace Improbable.Unity.Editor.Core +{ + /// + /// Manages the selection of the available workers in the Unity project. + /// + public class WorkerSelection + { + private readonly HashSet selectedWorkers = new HashSet(); + + /// + /// Invoked when the set of selected workers changes. + /// + public event Action> OnWorkerSelectionChanged; + + /// + /// Selects or deselects a worker. + /// + public void SelectWorker(SpatialOsWorker worker, bool selectWorker) + { + var changed = selectWorker ? selectedWorkers.Add(worker) : selectedWorkers.Remove(worker); + + if (changed && OnWorkerSelectionChanged != null) + { + OnWorkerSelectionChanged(selectedWorkers); + } + } + + /// + /// A set of selected worker instances. + /// + public HashSet SelectedWorkers + { + get { return new HashSet(selectedWorkers); } + } + + /// + /// Returns true if any workers are selected. + /// + public bool AnyWorkersSelected + { + get { return selectedWorkers.Count > 0; } + } + + /// + /// Returns true if the worker is selected. + /// + public bool IsWorkerSelected(SpatialOsWorker worker) + { + return selectedWorkers.Contains(worker); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/WorkerSelection.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/WorkerSelection.cs.meta new file mode 100644 index 0000000..8654ca0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Core/WorkerSelection.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c76827b39d38fbe4a82de46375bec7ed +timeCreated: 1484231158 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor.meta new file mode 100644 index 0000000..f833a64 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9556988323f446d4bb85777e81b94c09 +folderAsset: yes +timeCreated: 1471955846 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/.iOSBuildTools/XcodeSettingsBuilder.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/.iOSBuildTools/XcodeSettingsBuilder.cs new file mode 100644 index 0000000..54727ac --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/.iOSBuildTools/XcodeSettingsBuilder.cs @@ -0,0 +1,103 @@ + +using System.IO; +using UnityEditor; +using UnityEditor.iOS.Xcode; +using UnityEngine; + +namespace Improbable.Editor.Addons.iOSBuildTools +{ + /// + /// Configures and updates all Xcode build settings to be compatible with + /// SpatialOS and iOS Player build targets. + /// + + [InitializeOnLoad] + internal static class XcodeSettingsBuilder + { + private const string WORKER_BUILD_FOLDER = "build/worker"; + private const string WORKER_NAME = "UnityClient@iOS"; + private const string CORE_SDK_DLL_NAME = "libCoreSdkDll.dylib"; + + [MenuItem("Improbable/iOS/Update Xcode Build Settings (Device)")] + public static void UpdateXcodeBuildSettingsForDevice() + { + UpdateXcodeBuildSettings(GetCoreSdkDllFolderForDevice()); + } + + [MenuItem("Improbable/iOS/Update Xcode Build Settings (Simulator)")] + public static void UpdateXcodeBuildSettingsForSimulator() + { + UpdateXcodeBuildSettings(GetCoreSdkDllFolderForSimulator()); + } + + public static void UpdateXcodeBuildSettings(string dylibFolderPath) + { + var unityWorkerRoot = Path.Combine(Application.dataPath, ".."); + var iOSPlayerBuildFolder = Path.Combine(WORKER_BUILD_FOLDER, Path.Combine(WORKER_NAME, WORKER_NAME)); + var defaultBuildFolderPath = Path.Combine(unityWorkerRoot, iOSPlayerBuildFolder); + + UpdateXcodeBuildSettingsAt(defaultBuildFolderPath, dylibFolderPath); + } + + private static void UpdateXcodeBuildSettingsAt(string buildFolderPath, string dylibFolderPath) + { + // Get path to build/workers/UnityClient@iOS/UnityClient@iOS/Unity-iPhone.xcodeproj + string projectPath = PBXProject.GetPBXProjectPath(buildFolderPath); + + if (!File.Exists(projectPath)) + { + Debug.LogErrorFormat("Unable to find pbxproj file at: {0}", projectPath); + return; + } + + // Load the current Xcode project + PBXProject project = new PBXProject(); + project.ReadFromFile(projectPath); + + // Get Xcode Target GUID of "Unity-iPhone" + var targetGuid = project.TargetGuidByName("Unity-iPhone"); + + // Copy Core SDK .dylib from Assets/Plugins to Xcode project + var dylibSource = Path.Combine(dylibFolderPath, CORE_SDK_DLL_NAME); + var dylibTarget = Path.Combine(buildFolderPath, Path.Combine("Data", CORE_SDK_DLL_NAME)); + File.Copy(dylibSource, dylibTarget, true /* overwriteExisting */); + + // Add the .dylib to the Xcode project + var dylibGuid = project.AddFile(dylibTarget, Path.Combine("Data", CORE_SDK_DLL_NAME), PBXSourceTree.Source); + project.AddFileToBuild(targetGuid, dylibGuid); + + // Disable bitcode + project.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO"); + + // Add @executable_path to runtime search paths so that @rpath/libCoreSdkDll.dylib resolves to + // a path that can be located by Xcode and iOS devices. + project.SetBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "@executable_path"); + project.AddBuildProperty(targetGuid, "LD_RUNPATH_SEARCH_PATHS", "@executable_path/Data"); + + // Fix library search paths generated by Unity by ensuring that quotation marks are not placed + // around the search paths. + project.SetBuildProperty(targetGuid, "LIBRARY_SEARCH_PATHS", "$(inherited)"); + project.AddBuildProperty(targetGuid, "LIBRARY_SEARCH_PATHS", "$(SRCROOT)"); + project.AddBuildProperty(targetGuid, "LIBRARY_SEARCH_PATHS", "$(SRCROOT)/Libraries"); + + // Add the Data folder as a library search path to locate the libCoreSdkDll.dylib at link time. + project.AddBuildProperty(targetGuid, "LIBRARY_SEARCH_PATHS", "$(SRCROOT)/Data"); + + // Write all changes to the Xcode project file. + project.WriteToFile(projectPath); + + // Output a useful message. + Debug.LogFormat("Successfully updated Xcode project settings for: {0}", projectPath); + } + + private static string GetCoreSdkDllFolderForSimulator() + { + return Path.Combine(Application.dataPath, "Plugins/Improbable/darwin_ios-x86_64_dll"); + } + + private static string GetCoreSdkDllFolderForDevice() + { + return Path.Combine(Application.dataPath, "Plugins/Improbable/darwin_ios-arm_dll"); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/.iOSBuildTools/XcodeSettingsBuilder.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/.iOSBuildTools/XcodeSettingsBuilder.cs.meta new file mode 100644 index 0000000..a079d0e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/.iOSBuildTools/XcodeSettingsBuilder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 38db9d234f6d64c79b612c8e4eb6d834 +timeCreated: 1485438136 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialBuild.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialBuild.meta new file mode 100644 index 0000000..d03cf95 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialBuild.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 3bff1f3f1c725cd4ca7bb0a8c4003baf +folderAsset: yes +timeCreated: 1484060314 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialBuild/SpatialBuild.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialBuild/SpatialBuild.cs new file mode 100644 index 0000000..d693bb4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialBuild/SpatialBuild.cs @@ -0,0 +1,372 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Improbable.Unity.Editor.Addons; +using Improbable.Unity.Editor.Core; +using Improbable.Unity.EditorTools.Build; +using Improbable.Unity.EditorTools.PrefabExport; +using Improbable.Unity.EditorTools.Util; +using Improbable.Unity.Util; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Editor.Addons.SpatialBuild +{ + [InitializeOnLoad] + public class SpatialBuild : ISpatialOsEditorAddon, ISpatialOsEditorAddonSettings, ISpatialOsEditorAddonBuild + { + private readonly string[] configurations = { DevelopmentTarget, DeploymentTarget }; + + private int selectedConfiguration; + + private IList buildTargets; + private string selectedWorkerString; + + internal const string DeploymentTarget = "Deployment"; + internal const string DevelopmentTarget = "Development"; + + private readonly string selectedWorkerTargetPath = PathUtil.Combine(SpatialOsEditor.WorkerRootDir, "build", "player_build_target.txt"); + + /// + public string Name + { + get { return "Build"; } + } + + /// + public string Vendor + { + get { return "Improbable Worlds, Ltd."; } + } + + private string SelectedConfiguration + { + get { return configurations[selectedConfiguration]; } + } + + internal SpatialBuild() + { + try + { + var sourceText = File.ReadAllText(selectedWorkerTargetPath); + selectedConfiguration = System.Math.Max(0, System.Math.Min(int.Parse(sourceText), configurations.Length - 1)); + } + catch { } + + SpatialOsEditor.WorkerSelection.OnWorkerSelectionChanged += WorkerSelectionChanged; + } + + private void WorkerSelectionChanged(HashSet spatialOsWorkers) + { + buildTargets = null; + } + + private void EditorUpdate() + { + EditorApplication.update -= EditorUpdate; + BuildWorkers(); + } + + /// + public void OnDevGui(Rect rect) + { + using (new EditorGUILayout.VerticalScope()) + { + DrawWorkerTargetSelection(); + + using (new EditorGUI.DisabledScope(!SpatialOsEditor.WorkerSelection.AnyWorkersSelected || buildTargets == null || buildTargets.Count == 0)) + { + using (new EditorGUILayout.VerticalScope()) + { + GUILayout.Label("Generate from schema", EditorStyles.boldLabel); + + using (new EditorGUILayout.HorizontalScope()) + { + if (GUILayout.Button(new GUIContent("Build", "Generate code for the currently selected workers."), EditorStyles.toolbarButton)) + { + CodegenWorkers(); + } + + if (GUILayout.Button(new GUIContent("Cleanup", "Generate code for the currently selected workers."), EditorStyles.toolbarButton)) + { + CleanupCodegen(); + } + } + } + + EditorGUILayout.Separator(); + + using (new EditorGUILayout.VerticalScope()) + { + GUILayout.Label("Entity prefabs", EditorStyles.boldLabel); + + using (new EditorGUILayout.HorizontalScope()) + { + if (GUILayout.Button(new GUIContent("Build all", "Build entity prefabs for the selected workers and targets."), EditorStyles.toolbarButton)) + { + ExportPrefabs(); + } + + using (new EditorGUI.DisabledScope(!EntityPrefabExporter.AnyPrefabsSelected())) + { + if (GUILayout.Button(new GUIContent("Build selected", "Build selected entity prefabs for the selected workers and targets."), EditorStyles.toolbarButton)) + { + ExportSelectedPrefabs(); + } + } + + if (GUILayout.Button("Cleanup all", EditorStyles.toolbarButton)) + { + EntityPrefabExportMenus.OnCleanEntityPrefabs(); + } + } + } + + EditorGUILayout.Separator(); + + using (new EditorGUILayout.VerticalScope()) + { + GUILayout.Label("Workers", EditorStyles.boldLabel); + + using (new EditorGUILayout.HorizontalScope()) + { + if (GUILayout.Button(new GUIContent("Build", "Build the currently selected workers."), EditorStyles.toolbarButton)) + { + EditorApplication.update += EditorUpdate; + } + + if (GUILayout.Button(new GUIContent("Cleanup all", "Clean all workers."), EditorStyles.toolbarButton)) + { + UnityPathUtil.EnsureDirectoryClean(UnityPlayerBuilder.PlayerBuildDirectory); + UnityPathUtil.EnsureDirectoryClean(UnityPlayerBuilder.PlayerBuildScratchDirectory); + } + } + } + } + } + } + + public void OnSettingsGui(Rect rect) + { + var patchingEnabled = UnityPlayerBuilderMenu.IsAutopatchEnabled(); + if (patchingEnabled != EditorGUILayout.ToggleLeft("Autopatch workers", patchingEnabled)) + { + UnityPlayerBuilderMenu.ToggleAutopatch(); + } + } + + private void BuildWorkers() + { + var workersToBuild = GetSelectedWorkerNames(); + var sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + + try + { + switch (SelectedConfiguration) + { + case DevelopmentTarget: + UnityPlayerBuilders.BuildDevelopmentPlayers(workersToBuild); + break; + case DeploymentTarget: + UnityPlayerBuilders.BuildDeploymentPlayers(workersToBuild); + break; + default: + throw new ArgumentOutOfRangeException(string.Format("{0} is an unknown build target.", SelectedConfiguration)); + } + } + finally + { + sw.Stop(); + } + } + + private static void CodegenWorkers() + { + var selectedWorkers = string.Join(" ", SpatialOsEditor.WorkerSelection.SelectedWorkers.Select(w => w.Name).ToArray()); + SpatialOsEditor.RunPausedProcess(SpatialCommand.SpatialPath, "codegen " + selectedWorkers, SpatialOsEditor.WorkerRootDir); + } + + private static void CleanupCodegen() + { + var codegenOutputDir = PathUtil.Combine(Directory.GetCurrentDirectory(), EditorPaths.CodeGeneratorScratchDirectory).ToUnityPath(); + if (!Directory.Exists(codegenOutputDir)) + { + Debug.LogFormat("Code generator output directory '{0}' does not exist. Nothing to clean.", codegenOutputDir); + return; + } + + try + { + Debug.LogFormat("Removing code generator output from '{0}'.", codegenOutputDir); + Directory.Delete(codegenOutputDir, true); + } + catch (Exception e) + { + Debug.LogErrorFormat("Exception was thrown when cleaning codegen output."); + Debug.LogException(e); + } + } + + private void ExportPrefabs() + { + var targets = GetWorkerBuilders(); + foreach (var target in targets) + { + EntityPrefabExporter.ExportAllEntityPrefabs(target.BuildTarget); + } + } + + private void ExportSelectedPrefabs() + { + var targets = GetWorkerBuilders(); + foreach (var target in targets) + { + EntityPrefabExporter.ExportSelectedEntityPrefabs(target.BuildTarget); + } + } + + private void DrawWorkerTargetSelection() + { + using (new EditorGUILayout.VerticalScope()) + { + using (var check = new EditorGUI.ChangeCheckScope()) + using (new EditorGUILayout.HorizontalScope()) + { + selectedConfiguration = EditorGUILayout.Popup(selectedConfiguration, configurations); + if (check.changed) + { + buildTargets = null; + try + { + File.WriteAllText(selectedWorkerTargetPath, selectedConfiguration.ToString()); + } + catch { } + } + } + + using (new EditorGUILayout.VerticalScope()) + { + var workers = SpatialOsEditor.Workers; + if (workers.Count == 0) + { + GUILayout.Label("No workers found."); + return; + } + + for (var i = 0; i < workers.Count; i++) + { + var worker = workers[i]; + + using (var check = new EditorGUI.ChangeCheckScope()) + { + var selected = EditorGUILayout.ToggleLeft(worker.Name, SpatialOsEditor.WorkerSelection.IsWorkerSelected(worker)); + + if (check.changed) + { + SpatialOsEditor.WorkerSelection.SelectWorker(worker, selected); + } + } + } + } + } + + CalculateBuildTargets(); + + using (new EditorGUILayout.VerticalScope()) + { + EditorGUILayout.Separator(); + + GUILayout.Label("Output platforms", EditorStyles.boldLabel); + + if (buildTargets == null || buildTargets.Count == 0) + { + if (string.IsNullOrEmpty(selectedWorkerString)) + { + selectedWorkerString = string.Join(" and ", SpatialOsEditor.WorkerSelection.SelectedWorkers.Select(w => w.Name).ToArray()); + } + + EditorGUILayout.HelpBox(string.Format("The current selection of \"{0}\", filtered by the worker types \"{1}\", will not output any workers or prefabs.", SelectedConfiguration, selectedWorkerString), MessageType.Warning); + } + else + { + using (new EditorGUILayout.HorizontalScope()) + { + EditorGUILayout.Space(); + + using (new EditorGUILayout.VerticalScope()) + { + for (var i = 0; i < buildTargets.Count; i++) + { + { + GUILayout.Label(buildTargets[i]); + } + } + } + } + } + } + } + + private IList GetSelectedWorkerNames() + { + return SpatialOsEditor.WorkerSelection.SelectedWorkers.Select(w => w.Name).ToList(); + } + + private IList GetWorkerBuilders() + { + var workersToBuild = GetSelectedWorkerNames(); + + switch (SelectedConfiguration) + { + case DevelopmentTarget: + return UnityPlayerBuilders.DevelopmentPlayerBuilders(workersToBuild); + case DeploymentTarget: + return UnityPlayerBuilders.DeploymentPlayerBuilders(workersToBuild); + default: + throw new ArgumentOutOfRangeException(string.Format("{0} is an unknown build target.", SelectedConfiguration)); + } + } + + static SpatialBuild() + { + SpatialOsEditor.RegisterAddon(new SpatialBuild()); + } + + /// + /// Calculate and cache the set of based on the target type (Development|Deployment) and + /// selected workers. + /// + private void CalculateBuildTargets() + { + if (buildTargets == null) + { + if (!SpatialOsEditor.WorkerSelection.AnyWorkersSelected) + { + buildTargets = new List(); + } + else + { + var workersToBuild = GetSelectedWorkerNames(); + IList builders; + switch (SelectedConfiguration) + { + case DevelopmentTarget: + builders = UnityPlayerBuilders.DevelopmentPlayerBuilders(workersToBuild); + break; + case DeploymentTarget: + builders = UnityPlayerBuilders.DeploymentPlayerBuilders(workersToBuild); + break; + default: + throw new ArgumentOutOfRangeException(string.Format("{0} is an unknown build target.", SelectedConfiguration)); + } + + buildTargets = builders.Select(b => b.BuildTarget.ToString()).Distinct().OrderBy(b => b).ToList(); + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialBuild/SpatialBuild.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialBuild/SpatialBuild.cs.meta new file mode 100644 index 0000000..0d4c5e5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialBuild/SpatialBuild.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ce0a67b8a70d3de49bd5c9f8a0faea84 +timeCreated: 1484060314 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialConnect.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialConnect.meta new file mode 100644 index 0000000..21ac98d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialConnect.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: ae688f67d38883f46b380004ff8cbf31 +folderAsset: yes +timeCreated: 1481561131 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialConnect/SpatialConnect.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialConnect/SpatialConnect.cs new file mode 100644 index 0000000..8e60d99 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialConnect/SpatialConnect.cs @@ -0,0 +1,51 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Editor.Addons; +using Improbable.Unity.Editor.Core; +using Improbable.Unity.EditorTools; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Editor.Addons.SpatialConnect +{ + [InitializeOnLoad] + public class SpatialConnect : ISpatialOsEditorAddon, ISpatialOsEditorAddonBuild + { + private string deploymentName; + + /// + public string Name + { + get { return "Connect to deployment"; } + } + + /// + public string Vendor + { + get { return "Improbable Worlds, Ltd."; } + } + + static SpatialConnect() + { + SpatialOsEditor.RegisterAddon(new SpatialConnect()); + } + + /// + public void OnDevGui(Rect rect) + { + using (new EditorGUILayout.HorizontalScope()) + { + GUILayout.Label("Deployment name", SpatialOsWindow.SharedContent.MinimalLabelStyle); + deploymentName = EditorGUILayout.TextField(deploymentName); + + using (new EditorGUI.DisabledScope(string.IsNullOrEmpty(deploymentName))) + { + if (GUILayout.Button("Connect")) + { + SpatialOsEditor.RunPausedProcess(SpatialCommand.SpatialPath, string.Format("connect {0} {1}", SpatialOsEditor.ProjectDescriptor.Name, deploymentName), SpatialOsEditor.ApplicationRootDir); + } + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialConnect/SpatialConnect.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialConnect/SpatialConnect.cs.meta new file mode 100644 index 0000000..ddd8eba --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialConnect/SpatialConnect.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9477feca0c8392d4cb6e1ccc650c8abf +timeCreated: 1481561131 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialLocal.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialLocal.meta new file mode 100644 index 0000000..c241d07 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialLocal.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 0c36411904fd30d47922675f7f5b9b43 +folderAsset: yes +timeCreated: 1481560970 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialLocal/SpatialLocal.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialLocal/SpatialLocal.cs new file mode 100644 index 0000000..bf55625 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialLocal/SpatialLocal.cs @@ -0,0 +1,54 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.IO; +using System.Linq; +using Improbable.Unity.Editor.Addons; +using Improbable.Unity.Editor.Core; +using Improbable.Unity.Util; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Editor.Addons.SpatialLocal +{ + [InitializeOnLoad] + public class SpatialLocal : ISpatialOsEditorAddon, ISpatialOsEditorAddonBuild + { + public string Name + { + get { return "Run SpatialOS locally"; } + } + + public string Vendor + { + get { return "Improbable Worlds, Ltd."; } + } + + private int selectedConfig; + + static SpatialLocal() + { + SpatialOsEditor.RegisterAddon(new SpatialLocal()); + } + + public void OnDevGui(Rect rect) + { + using (new EditorGUILayout.VerticalScope()) + { + var rootPath = Path.GetFullPath(PathUtil.Combine(SpatialOsEditor.WorkerRootDir, "..", "..")); + var files = Directory.GetFiles(rootPath, "*.json"); + var filtered = files.Where(f => !f.Contains("spatialos")).Select(Path.GetFileName).ToArray(); + selectedConfig = GUILayout.SelectionGrid(selectedConfig, filtered, 1); + + using (new EditorGUI.DisabledScope(selectedConfig < 0 || selectedConfig >= filtered.Length)) + { + EditorGUILayout.Space(); + + if (GUILayout.Button("Run")) + { + SpatialOsEditor.RunPausedProcess(SpatialCommand.SpatialPath, string.Format("local start {0}", filtered[selectedConfig]), rootPath); + } + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialLocal/SpatialLocal.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialLocal/SpatialLocal.cs.meta new file mode 100644 index 0000000..45aa0e8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialLocal/SpatialLocal.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9c2ce9381a58c9a448091a1bf237968c +timeCreated: 1481561131 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialUpload.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialUpload.meta new file mode 100644 index 0000000..cd3ff2e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialUpload.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 1b2f275bec3ac674d9596dcc0636ea67 +folderAsset: yes +timeCreated: 1481621743 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialUpload/SpatialUpload.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialUpload/SpatialUpload.cs new file mode 100644 index 0000000..8fb5ecd --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialUpload/SpatialUpload.cs @@ -0,0 +1,48 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Editor.Addons; +using Improbable.Unity.Editor.Core; +using Improbable.Unity.EditorTools; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Editor.Addons.SpatialUpload +{ + [InitializeOnLoad] + public class SpatialUpload : ISpatialOsEditorAddon, ISpatialOsEditorAddonBuild + { + private string assemblyName; + + /// + public string Name + { + get { return "Spatial upload"; } + } + + /// + public string Vendor + { + get { return "Improbable Worlds, Ltd."; } + } + + /// + public void OnDevGui(Rect rect) + { + using (new EditorGUILayout.HorizontalScope()) + { + GUILayout.Label("Assembly name", SpatialOsWindow.SharedContent.MinimalLabelStyle); + assemblyName = EditorGUILayout.TextField(assemblyName); + + if (GUILayout.Button("Upload")) + { + SpatialOsEditor.RunPausedProcess(SpatialCommand.SpatialPath, "upload " + assemblyName, SpatialOsEditor.ApplicationRootDir); + } + } + } + + static SpatialUpload() + { + SpatialOsEditor.RegisterAddon(new SpatialUpload()); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialUpload/SpatialUpload.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialUpload/SpatialUpload.cs.meta new file mode 100644 index 0000000..e62c4ed --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Editor/SpatialUpload/SpatialUpload.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4b455904a16647847912067b817b9a24 +timeCreated: 1481621744 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Init.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Init.meta new file mode 100644 index 0000000..63d8e1d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Init.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 79b7354d6621ea544be9f48865593bbe +folderAsset: yes +timeCreated: 1444833351 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Init/InitializeSDKLogging.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Init/InitializeSDKLogging.cs new file mode 100644 index 0000000..07089cc --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Init/InitializeSDKLogging.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Worker; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Init +{ + [InitializeOnLoad] + class InitializeSDKLogging + { + static InitializeSDKLogging() + { + ClientError.ExceptionCallback = Debug.LogException; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Init/InitializeSDKLogging.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Init/InitializeSDKLogging.cs.meta new file mode 100644 index 0000000..83faa7a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Init/InitializeSDKLogging.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 98433a43b744a6c439323bb46b6ae27d +timeCreated: 1496916327 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Internal.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Internal.meta new file mode 100644 index 0000000..c0d837b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Internal.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 02f905050e9f2a14a8ec074d702369e3 +folderAsset: yes +timeCreated: 1515492928 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Internal/EditorObjectStateManager.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Internal/EditorObjectStateManager.cs new file mode 100644 index 0000000..ed66264 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Internal/EditorObjectStateManager.cs @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; + +namespace Improbable.Unity.CodeGeneration +{ + /// + /// Utility class for storing state of Unity editor components. + /// + public static class EditorObjectStateManager + { + private static readonly IDictionary EditorDataObjects = + new Dictionary(); + + public static IComponentEditorDataObject GetComponentEditorData(int objectHash) + { + IComponentEditorDataObject value; + return EditorDataObjects.TryGetValue(objectHash, out value) ? value : null; + } + + public static void SetComponentEditorData(int objectHash, IComponentEditorDataObject editorData) + { + EditorDataObjects[objectHash] = editorData; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Internal/EditorObjectStateManager.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Internal/EditorObjectStateManager.cs.meta new file mode 100644 index 0000000..b2ca20c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Internal/EditorObjectStateManager.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e4b5478947f037c41a6aba7c35e686bc +timeCreated: 1490268491 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport.meta new file mode 100644 index 0000000..b25b0ab --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: f19c68b39cdc139438cb255c9f485018 +folderAsset: yes +timeCreated: 1444833352 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabDirectoryCleaner.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabDirectoryCleaner.cs new file mode 100644 index 0000000..98e015b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabDirectoryCleaner.cs @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.IO; +using Improbable.Unity.EditorTools.Util; + +namespace Improbable.Unity.EditorTools.PrefabExport +{ + static class EntityPrefabDirectoryCleaner + { + public static void CleanPrefabTargetDirectories() + { + var info = new DirectoryInfo(EditorPaths.PrefabExportDirectory); + if (info.Exists) + { + var files = info.GetFiles(); + foreach (var fileInfo in files) + { + fileInfo.Delete(); + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabDirectoryCleaner.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabDirectoryCleaner.cs.meta new file mode 100644 index 0000000..06134c9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabDirectoryCleaner.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e3f760e3de3c4f24faeb90d5ae22fbb9 +timeCreated: 1444833362 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExportMenus.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExportMenus.cs new file mode 100644 index 0000000..bf7b9c8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExportMenus.cs @@ -0,0 +1,85 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using UnityEditor; + +namespace Improbable.Unity.EditorTools.PrefabExport +{ + public class EntityPrefabExportMenus + { + /// + /// This is called whenever entity prefabs need to be exported for all build targets. + /// This can be done from within the editor, or from external sources like build systems. + /// By default its value is the baseline behaviour, which can be saved off and invoked + /// as part of a custom chain of events. + /// + public static Action OnExportAllEntityPrefabs = EntityPrefabExporter.ExportAllEntityPrefabs; + + /// + /// This is called whenever entity prefabs need to be exported for development targets. + /// This can be done from within the editor, or from external sources like build systems. + /// By default its value is the baseline behaviour, which can be saved off and invoked + /// as part of a custom chain of events. + /// + public static Action OnExportDevelopmentEntityPrefabs = EntityPrefabExporter.ExportDevelopmentEntityPrefabs; + + /// + /// This is called when we need to export the entity prefabs that are currently selected in the Project view. + /// + public static Action OnExportSelectedEntityPrefabs = EntityPrefabExporter.ExportSelectedEntityPrefabs; + + /// + /// This is called when we need to export the entity prefabs that are currently selected in the Project view. + /// + public static Action OnExportSelectedDevelopmentEntityPrefabs = EntityPrefabExporter.ExportSelectedDevelopmentEntityPrefabs; + + /// + /// This is called whenever entity prefabs need to be cleaned. + /// This can be done from within the editor, or from external sources like build systems. + /// By default its value is the baseline behaviour, which can be saved off and invoked + /// as part of a custom chain of events. + /// + public static Action OnCleanEntityPrefabs = EntityPrefabDirectoryCleaner.CleanPrefabTargetDirectories; + + [MenuItem("Improbable/Prefabs/Clean entity prefabs")] + public static void CleanAllEntityPrefabs() + { + OnCleanEntityPrefabs(); + } + + [MenuItem("Improbable/Prefabs/Export all entity prefabs %&#E")] + [MenuItem("Assets/Export all entity prefabs %&#E")] + public static void ExportAllEntityPrefabs() + { + OnExportAllEntityPrefabs(); + } + + [MenuItem("Improbable/Prefabs/Export development entity prefabs")] + [MenuItem("Assets/Export development entity prefabs")] + public static void ExportDevelopmentEntityPrefabs() + { + OnExportDevelopmentEntityPrefabs(); + } + + [MenuItem("Improbable/Prefabs/Export selected entity prefabs %&#S")] + [MenuItem("Assets/Export selected entity prefabs %&#S")] + public static void ExportSelectedEntityPrefabs() + { + OnExportSelectedEntityPrefabs(); + } + + [MenuItem("Improbable/Prefabs/Export selected development entity prefabs")] + [MenuItem("Assets/Export selected development entity prefabs")] + public static void ExportSelectedDevelopmentEntityPrefabs() + { + OnExportSelectedDevelopmentEntityPrefabs(); + } + + [MenuItem("Improbable/Prefabs/Export selected entity prefabs %&#S", true)] + [MenuItem("Assets/Export selected entity prefabs %&#S", true)] + private static bool ExportSelectedEntityPrefabsValidation() + { + return EntityPrefabExporter.AnyPrefabsSelected(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExportMenus.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExportMenus.cs.meta new file mode 100644 index 0000000..f6c8774 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExportMenus.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1225978330de5fd4f80065a7e395f675 +timeCreated: 1444833356 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExporter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExporter.cs new file mode 100644 index 0000000..ee1fce4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExporter.cs @@ -0,0 +1,200 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Improbable.Assets; +using Improbable.Unity.EditorTools.Build; +using Improbable.Unity.EditorTools.Util; +using Improbable.Unity.Util; +using UnityEditor; + +namespace Improbable.Unity.EditorTools.PrefabExport +{ + public class EntityPrefabExporter + { + private static readonly Dictionary buildTargetToBuildPlatform = + new Dictionary + { + { BuildTarget.StandaloneWindows, Platform.BuildPlatform.Windows }, + { BuildTarget.StandaloneWindows64, Platform.BuildPlatform.Windows }, +#if UNITY_2017_3_OR_NEWER + { BuildTarget.StandaloneOSX, Platform.BuildPlatform.OSX }, +#else + { BuildTarget.StandaloneOSXIntel, Platform.BuildPlatform.OSX }, + { BuildTarget.StandaloneOSXIntel64, Platform.BuildPlatform.OSX }, + { BuildTarget.StandaloneOSXUniversal, Platform.BuildPlatform.OSX }, +#endif + { BuildTarget.StandaloneLinux, Platform.BuildPlatform.Linux }, + { BuildTarget.StandaloneLinux64, Platform.BuildPlatform.Linux }, + { BuildTarget.iOS, Platform.BuildPlatform.iOS } + }; + + /// + /// Exports all entity prefabs for Development and Deployment, for each worker type. + /// + private static void ExportEntityPrefabs(IEnumerable prefabGuids, IEnumerable buildTargets) + { + EnsureDirectoriesExist(); + + var guids = prefabGuids.ToList(); + + foreach (var target in buildTargets) + { + ExportEntityPrefabs(guids, target); + } + } + + private static void EnsureDirectoriesExist() + { + PathUtil.EnsureDirectoryExists(EditorPaths.PrefabExportDirectory); + PathUtil.EnsureDirectoryExists(EditorPaths.PrefabSourceDirectory); + } + + public static void ExportEntityPrefabs(IEnumerable prefabGuids, BuildTarget buildTarget) + { + RemoveAssetBundleNamesFromAllPrefabs(); + AddAssetBundleNamesToPrefabs(prefabGuids, buildTarget); + AssetDatabase.SaveAssets(); + ExportAllNamedPrefabs(buildTarget); + RemoveAssetBundleNamesFromAllPrefabs(); + } + + public static void ExportAllEntityPrefabs() + { + var buildTargets = GetAllBuildTargets(); + ExportEntityPrefabs(GetAllPrefabAssetGuids(), buildTargets); + } + + public static void ExportAllEntityPrefabs(BuildTarget target) + { + ExportEntityPrefabs(GetAllPrefabAssetGuids(), new List { target }); + } + + public static void ExportDevelopmentEntityPrefabs() + { + var targets = UnityPlayerBuilders.DevelopmentPlayerBuilders(SimpleBuildSystem.AllWorkerTypes) + .Select(b => b.BuildTarget) + .Distinct() + .ToList(); + ExportEntityPrefabs(GetAllPrefabAssetGuids(), targets); + } + + public static void ExportSelectedEntityPrefabs() + { + var buildTargets = GetAllBuildTargets(); + ExportEntityPrefabs(GetSelectedPrefabAssetGuids(), buildTargets); + } + + public static void ExportSelectedEntityPrefabs(BuildTarget buildTarget) + { + ExportEntityPrefabs(GetSelectedPrefabAssetGuids(), new List { buildTarget }); + } + + public static void ExportSelectedDevelopmentEntityPrefabs() + { + var targets = UnityPlayerBuilders.DevelopmentPlayerBuilders(SimpleBuildSystem.AllWorkerTypes) + .Select(b => b.BuildTarget) + .Distinct() + .ToList(); + ExportEntityPrefabs(GetSelectedPrefabAssetGuids(), targets); + } + + public static IEnumerable GetAllPrefabAssetGuids() + { + List dirsToSearch = new List { EditorPaths.PrefabSourceDirectory }; + + if (Directory.Exists(EditorPaths.PrefabResourcesDirectory)) + { + dirsToSearch.Add(EditorPaths.PrefabResourcesDirectory); + } + + return AssetDatabase.FindAssets("t:prefab", dirsToSearch.ToArray()).Where(IsPrefab); + } + + public static IEnumerable GetSelectedPrefabAssetGuids() + { + return GetAllPrefabAssetGuids().Where(s => Selection.assetGUIDs.Contains(s)); + } + + public static bool AnyPrefabsSelected() + { + for (var i = 0; i < Selection.assetGUIDs.Length; i++) + { + var guid = Selection.assetGUIDs[i]; + var path = AssetDatabase.GUIDToAssetPath(guid); + if (path.EndsWith(".prefab") && (path.StartsWith(EditorPaths.PrefabSourceDirectory) || path.StartsWith(EditorPaths.PrefabResourcesDirectory))) + { + return true; + } + } + + return false; + } + + private static IList GetAllBuildTargets() + { + var allTargets = UnityPlayerBuilders.DeploymentPlayerBuilders(SimpleBuildSystem.AllWorkerTypes) + .Union(UnityPlayerBuilders.DevelopmentPlayerBuilders(SimpleBuildSystem.AllWorkerTypes)) + .Select(b => b.BuildTarget) + .Distinct() + .ToList(); + + // Only export iOS prefabs if building for iOS. + if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) + { + allTargets.Add(BuildTarget.iOS); + } + + return allTargets; + } + + private static bool IsPrefab(string guid) + { + return AssetDatabase.GUIDToAssetPath(guid).EndsWith(".prefab"); + } + + private static void ExportAllNamedPrefabs(BuildTarget buildTarget) + { + BuildPipeline.BuildAssetBundles(EditorPaths.PrefabExportDirectory, + BuildAssetBundleOptions.UncompressedAssetBundle, + buildTarget); + } + + private static void AddAssetBundleNamesToPrefabs(IEnumerable guids, BuildTarget buildTarget) + { + if (!buildTargetToBuildPlatform.ContainsKey(buildTarget)) + { + throw new ArgumentException(string.Format("No build platform is associated with build target {0}.", buildTarget)); + } + + var buildPlatform = buildTargetToBuildPlatform[buildTarget]; + + foreach (var guid in guids) + { + var path = AssetDatabase.GUIDToAssetPath(guid); + var importer = AssetImporter.GetAtPath(path); + var name = Path.GetFileNameWithoutExtension(path); + if (name == null) + { + throw new InvalidOperationException(string.Format("Asset path is invalid {0}", path)); + } + + importer.assetBundleName = string.Format("{0}@{1}", name.ToLowerInvariant(), Platform.BuildPlatformToAssetBundleSuffix(buildPlatform)); + } + } + + private static void RemoveAssetBundleNamesFromAllPrefabs() + { + var prefabGuids = GetAllPrefabAssetGuids(); + foreach (var guid in prefabGuids) + { + var path = AssetDatabase.GUIDToAssetPath(guid); + var importer = AssetImporter.GetAtPath(path); + importer.assetBundleName = null; + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExporter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExporter.cs.meta new file mode 100644 index 0000000..abac56d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/PrefabExport/EntityPrefabExporter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ef89bc6190d1ca2418b44113dbf5eb25 +timeCreated: 1444833363 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util.meta new file mode 100644 index 0000000..7383eb6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 21374d5b0c6d381418c4499ab5b65ad4 +folderAsset: yes +timeCreated: 1444833351 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorPaths.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorPaths.cs new file mode 100644 index 0000000..300cd17 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorPaths.cs @@ -0,0 +1,33 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using Improbable.Unity.Util; + +namespace Improbable.Unity.EditorTools.Util +{ + /// + /// Contains common directories related to building assets and players. + /// + /// + /// All directories should be in Unity path format e.g. "Foo/Bar". + /// + public static class EditorPaths + { + public static readonly string OrganizationName = "Improbable"; + + public static readonly string PluginDirectory = PathUtil.Combine("Assets", "Plugins").ToUnityPath(); + + public static readonly string DataDirectory = PathUtil.Combine("..", "..", "build").ToUnityPath(); + public static readonly string AssetDatabaseDirectory = PathUtil.Combine(DataDirectory, "assembly").ToUnityPath(); + public static readonly string AssetDirectory = PathUtil.Combine(PluginDirectory, OrganizationName).ToUnityPath(); + + public static readonly string PrefabCompileDirectory = PathUtil.Combine(AssetDirectory, "EntityPrefabs").ToUnityPath(); + public static readonly string PrefabExportDirectory = PathUtil.Combine(AssetDatabaseDirectory, "unity").ToUnityPath(); + public static readonly string PrefabResourcesDirectory = PathUtil.Combine("Assets", "Resources", "EntityPrefabs").ToUnityPath(); + public static readonly string PrefabSourceDirectory = PathUtil.Combine("Assets", "EntityPrefabs").ToUnityPath(); + + public static readonly string ScriptAssembliesDirectory = PathUtil.Combine("Library", "ScriptAssemblies").ToUnityPath(); + + public static readonly string CodeGeneratorScratchDirectory = ".spatialos"; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorPaths.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorPaths.cs.meta new file mode 100644 index 0000000..5514371 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorPaths.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 32a181b1f3584f94caf51ac91bd7005b +timeCreated: 1444833357 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorTaskRunnerWithRetries.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorTaskRunnerWithRetries.cs new file mode 100644 index 0000000..48bd34c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorTaskRunnerWithRetries.cs @@ -0,0 +1,88 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Util +{ + /// + /// Utility class for running tasks with timestamp based retries. Suitable for situations where coroutines are not + /// available. + /// + internal static class EditorTaskRunnerWithRetries + { + public const int DefaultNumRetries = 1; + public static readonly TimeSpan RetryPeriod = TimeSpan.FromSeconds(10); + + private static Queue failedTaskRetryQueue = new Queue(); + private static IList bufferedExceptions = new List(); + + public static void RunTask(Action task, int retries = DefaultNumRetries, bool throwExceptions = true) + { + try + { + task(); + } + catch + { + if (retries > 0) + { + Retry(task, retries - 1, throwExceptions); + } + else + { + if (throwExceptions) + { + throw; + } + } + } + } + + private static void Retry(Action task, int remainingRetries, bool throwExceptions) + { + if (failedTaskRetryQueue.Count == 0) + { + EditorApplication.update += EditorUpdate; + } + + var failedTask = new FailedEditorTask(task, EditorApplication.timeSinceStartup, remainingRetries, throwExceptions); + failedTaskRetryQueue.Enqueue(failedTask); + } + + private static void EditorUpdate() + { + bufferedExceptions.Clear(); + + while (failedTaskRetryQueue.Count > 0 && RetryPeriodHasElapsed(failedTaskRetryQueue.Peek())) + { + var failedTask = failedTaskRetryQueue.Dequeue(); + try + { + RunTask(failedTask.Task, failedTask.RemainingRetries, failedTask.ShouldThrowExceptions); + } + catch (Exception e) + { + bufferedExceptions.Add(e); + } + } + + if (failedTaskRetryQueue.Count == 0) + { + EditorApplication.update -= EditorUpdate; + } + + for (var i = 0; i < bufferedExceptions.Count; i++) + { + Debug.LogException(bufferedExceptions[i]); + } + } + + private static bool RetryPeriodHasElapsed(FailedEditorTask task) + { + return task.TimeStamp <= EditorApplication.timeSinceStartup - RetryPeriod.Seconds; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorTaskRunnerWithRetries.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorTaskRunnerWithRetries.cs.meta new file mode 100644 index 0000000..30cd149 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/EditorTaskRunnerWithRetries.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7649f402bc6dfaf4faf500e433976e00 +timeCreated: 1504692725 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/FailedEditorTask.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/FailedEditorTask.cs new file mode 100644 index 0000000..cbe199c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/FailedEditorTask.cs @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.EditorTools.Util +{ + /// + /// Utility companion class for EditorTaskRunnerWithRetries. + /// + internal class FailedEditorTask + { + public readonly Action Task; + public readonly double TimeStamp; + public readonly int RemainingRetries; + public readonly bool ShouldThrowExceptions; + + public FailedEditorTask(Action task, double timeStamp, int remainingRetries, bool shouldThrowExceptions) + { + Task = task; + TimeStamp = timeStamp; + RemainingRetries = remainingRetries; + ShouldThrowExceptions = shouldThrowExceptions; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/FailedEditorTask.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/FailedEditorTask.cs.meta new file mode 100644 index 0000000..0e63ebb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/FailedEditorTask.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6c96b3bc8049bb048b999980e953d21b +timeCreated: 1504703108 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/GuiUtil.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/GuiUtil.cs new file mode 100644 index 0000000..519301d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/GuiUtil.cs @@ -0,0 +1,13 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Util +{ + public class GuiUtil + { + public static readonly GUIStyle GreenTextStyle = new GUIStyle { normal = { textColor = Color.green } }; + public static readonly GUIStyle RedTextStyle = new GUIStyle { normal = { textColor = Color.red } }; + public static readonly GUIStyle YellowTextStyle = new GUIStyle { normal = { textColor = Color.yellow } }; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/GuiUtil.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/GuiUtil.cs.meta new file mode 100644 index 0000000..111e0ca --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/GuiUtil.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 74ae124032a5d7141b61d73ca257b0ed +timeCreated: 1485356049 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/JsonUtil.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/JsonUtil.cs new file mode 100644 index 0000000..3e2797f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/JsonUtil.cs @@ -0,0 +1,81 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Improbable.Unity.EditorTools.Util +{ + internal static class JsonUtil + { + /// + /// Return a JSON string representation of an IDictionary. + /// + internal static string ToJson(this IDictionary dict) + { + if (dict == null || dict.Count == 0) + { + return "{}"; + } + + var stringBuilder = new StringBuilder(); + stringBuilder.Append('{'); + stringBuilder.Append(string.Join(",", dict.Select(pair => string.Format("\"{0}\":\"{1}\"", Escape(pair.Key), Escape(pair.Value))).ToArray())); + stringBuilder.Append('}'); + return stringBuilder.ToString(); + } + + /// + /// Escapes the given text string for use in JSON. + /// + /// + /// Escapes characters based on the ECMA-404 Standard (JSON.org) + /// http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf + /// + internal static string Escape(string text) + { + StringWriter stringWriter = new StringWriter(); + foreach (var c in text) + { + switch (c) + { + case '"': + stringWriter.Write("\\\""); + break; + case '\\': + stringWriter.Write("\\\\"); + break; + case '\b': + stringWriter.Write("\\b"); + break; + case '\f': + stringWriter.Write("\\f"); + break; + case '\n': + stringWriter.Write("\\n"); + break; + case '\r': + stringWriter.Write("\\r"); + break; + case '\t': + stringWriter.Write("\\t"); + break; + default: + if ('\x00' <= c && c <= '\x1f') + { + stringWriter.Write("\\u" + string.Format("{0:X}", (int) c).PadLeft(4, '0')); + } + else + { + stringWriter.Write(c); + } + + break; + } + } + + return stringWriter.ToString(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/JsonUtil.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/JsonUtil.cs.meta new file mode 100644 index 0000000..d882dba --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/JsonUtil.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 59daeb460721dcd4e8eff8b8698a317c +timeCreated: 1504117491 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/SerializableDictionary.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/SerializableDictionary.cs new file mode 100644 index 0000000..5333240 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/SerializableDictionary.cs @@ -0,0 +1,51 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Unity.EditorTools.Util +{ + /// + /// Unity's serialization system can't handle Dictionary by default. + /// Users must inherit their from this type to serialize it. + /// + [Serializable] + internal class SerializableDictionary : Dictionary, ISerializationCallbackReceiver + { + // These fields must not be readonly, or serialization will not work. + + // ReSharper disable once FieldCanBeMadeReadOnly.Local + [SerializeField] private List keys = new List(); + + // ReSharper disable once FieldCanBeMadeReadOnly.Local + [SerializeField] private List values = new List(); + + public void OnBeforeSerialize() + { + keys.Clear(); + values.Clear(); + + foreach (var pair in this) + { + keys.Add(pair.Key); + values.Add(pair.Value); + } + } + + public void OnAfterDeserialize() + { + Clear(); + + if (keys.Count != values.Count) + { + throw new Exception(string.Format("there are {0} keys and {1} values after deserialization. Make sure that both key and value types are [Serializable].", keys.Count, values.Count)); + } + + for (int i = 0; i < keys.Count; i++) + { + Add(keys[i], values[i]); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/SerializableDictionary.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/SerializableDictionary.cs.meta new file mode 100644 index 0000000..4f0a3da --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/SerializableDictionary.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 838b03387e0ed71409cd7ec55a037abe +timeCreated: 1494600098 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/TempFolder.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/TempFolder.cs new file mode 100644 index 0000000..ef425f1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/TempFolder.cs @@ -0,0 +1,45 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.IO; + +namespace Improbable.Unity.EditorTools.Util +{ + public class TempFolder : IDisposable + { + private string path; + + public String Path + { + get + { + if (!Initialized) + { + path = CreateTempPath(); + } + + return path; + } + } + + private bool Initialized + { + get { return !string.IsNullOrEmpty(path); } + } + + public void Dispose() + { + if (Initialized) + { + Directory.Delete(path, true); + } + } + + public string CreateTempPath() + { + string tempDirectory = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()); + Directory.CreateDirectory(tempDirectory); + return tempDirectory; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/TempFolder.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/TempFolder.cs.meta new file mode 100644 index 0000000..5cf5ada --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/TempFolder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2cb066212a2c59b43a2301146a783a34 +timeCreated: 1444833357 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/UnityPathUtil.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/UnityPathUtil.cs new file mode 100644 index 0000000..26a8779 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/UnityPathUtil.cs @@ -0,0 +1,55 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.IO; +using Improbable.Unity.Util; + +namespace Improbable.Unity.EditorTools.Util +{ + public static class UnityPathUtil + { + public static void EnsureDirectoryClean(string directory) + { + EnsureDirectoryRemoved(directory); + PathUtil.EnsureDirectoryExists(directory); + } + + public static void EnsureDirectoryRemoved(string directory) + { + if (Directory.Exists(directory)) + { + var directoryInfo = new DirectoryInfo(directory); + + foreach (var file in directoryInfo.GetFiles("*", SearchOption.AllDirectories)) + { + file.Attributes &= ~FileAttributes.ReadOnly; + } + + Directory.Delete(directory, true); + DeleteMetaFile(directory); + } + } + + public static void EnsureFileRemoved(string file) + { + if (File.Exists(file)) + { + DeleteFile(file); + } + } + + public static void DeleteFile(string file) + { + File.Delete(file); + DeleteMetaFile(file); + } + + public static void DeleteMetaFile(string path) + { + var metaPath = path + ".meta"; + if (File.Exists(metaPath)) + { + File.Delete(metaPath); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/UnityPathUtil.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/UnityPathUtil.cs.meta new file mode 100644 index 0000000..67912cd --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Editor/Util/UnityPathUtil.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bd8f01b859b3f9f459016921f8d78e24 +timeCreated: 1454508522 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity.meta new file mode 100644 index 0000000..7abf1e5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 6476a33cc249f3d4c9d26dea32a4c757 +folderAsset: yes +timeCreated: 1526049922 +licenseType: Pro +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets.meta new file mode 100644 index 0000000..d813c70 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c8ad01ea56b15c849aea76f8ac408cb5 +folderAsset: yes +timeCreated: 1444833351 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetBundleDownloader.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetBundleDownloader.cs new file mode 100644 index 0000000..a396361 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetBundleDownloader.cs @@ -0,0 +1,294 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Serialization; +using Improbable.Assets; +using Improbable.Unity.Util; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + public class AssetBundleDownloader : IAssetLoader + { + private IMachineCache assetCache; + private IMachineCache metaDataCache; + private string cachePath; + private IWWWRequest wwwRequest; + private MonoBehaviour coroutineHost; + + private readonly HashSet pendingDownloadRequests = new HashSet(); + + /// + /// The method to invoke to resolve a prefab name to a URL. Throws a if the asset + /// is unknown. + /// + public Func GetAssetUrl { get; set; } + + public AssetBundleDownloader() + { + GetAssetUrl = s => { throw new InvalidOperationException("GetAssetUrl has not been assigned."); }; + } + + internal AssetBundleDownloader(string cachePath, IMachineCache assetCache, IMachineCache metaDataCache, IWWWRequest wwwRequest, MonoBehaviour coroutineHost) + { + this.cachePath = cachePath; + this.assetCache = assetCache; + this.metaDataCache = metaDataCache; + this.wwwRequest = wwwRequest; + this.coroutineHost = coroutineHost; + } + + /// + public void LoadAsset(string prefabName, Action onAssetLoaded, Action onError) + { + try + { + var assetUri = GetAssetUrl(prefabName); + + Action callback = (response) => { HandleDownloadAssetResponse(assetUri, onAssetLoaded, onError, response); }; + + DownloadAsync(wwwRequest, assetUri, assetUri, callback); + } + catch (Exception ex) + { + onError(ex); + } + } + + public void CancelAllLoads() + { + foreach (var requestId in pendingDownloadRequests) + { + requestId.Cancel(); + } + + pendingDownloadRequests.Clear(); + } + + private void HandleDownloadAssetResponse(string url, Action onAssetLoaded, Action onError, WWWResponse response) + { + if (!String.IsNullOrEmpty(response.Error)) + { + Debug.LogWarningFormat("Requesting asset '{0}' failed. Url: {1}. Error: {2}", GetAssetName(url), url, response.Error); + onError(new ApplicationException(response.Error)); + } + else if (response.ResponseHeaders.ContainsKey("STATUS") && response.ResponseHeaders["STATUS"].Contains("304")) + { + AssetBundle assetBundle; + if (assetCache.TryGet(GetAssetName(url), out assetBundle)) + { + onAssetLoaded(assetBundle); + } + else + { + Debug.LogErrorFormat("Failed to load asset '{0}' from cache. Corrupted cache. Please delete the cache folder: '{1}'.", GetAssetName(url), cachePath); + onError(new ApplicationException("Cache likely corrupted.")); + } + } + else if (response.AssetBundle != null) + { + UpdateCache(url, response); + onAssetLoaded(response.AssetBundle); + } + else + { + string responseHeadersString = ""; + foreach (var responseHeader in response.ResponseHeaders) + { + responseHeadersString += " [" + responseHeader.Key + ": " + responseHeader.Value + "]"; + } + + Debug.LogWarningFormat("Unhandled response for {0}. Downloaded {1} bytes, got the following response headers: {2}", url, response.BytesDownloaded, responseHeadersString); + onError(new ApplicationException("Unhandled response.")); + } + } + + private static string GetAssetName(string assetUrl) + { + return new Uri(assetUrl).PathAndQuery; + } + + private void UpdateCache(string url, WWWResponse response) + { + var added = assetCache.TryAddOrUpdate(GetAssetName(url), response.Bytes); + + if (added) + { + AddMetaDataCacheEntry(url, response); + } + else + { + Debug.LogWarningFormat("Failed to cache: {0}", url); + } + } + + private void AddMetaDataCacheEntry(string url, WWWResponse response) + { + if (!HasValidators(response)) + { + Debug.LogWarningFormat("ETag and/or Last-Modified missing. Cannot cache url {0}, keys {1}", url, string.Join("\n", response.ResponseHeaders.Keys.ToArray())); + } + else + { + var entry = CreateMetaDataCacheEntry(url, response); + if (!metaDataCache.TryAddOrUpdate(GetAssetName(url), entry)) + { + Debug.LogWarningFormat("Failed to add cache metadata for: {0}", url); + } + } + } + + private static bool HasValidators(WWWResponse response) + { + return response.ResponseHeaders.ContainsKey("LAST-MODIFIED") && response.ResponseHeaders.ContainsKey("ETAG"); + } + + private static CacheEntry CreateMetaDataCacheEntry(string url, WWWResponse response) + { + string responseHeader = response.ResponseHeaders["LAST-MODIFIED"]; + string entityTag = response.ResponseHeaders["ETAG"]; + var cacheEntry = new CacheEntry { EntityTag = entityTag, Modified = responseHeader, Url = url, LastFetched = DateTime.UtcNow }; + return cacheEntry; + } + + private void DownloadAsync(IWWWRequest wwwRequest, string originalUrl, string redirectedUrl, Action callback) + { + WWWRequestWrapper requestWrapper; + + // It is possible for 'SendGetRequest' or 'SendPostRequest' to have completed immediately. + // If this is the case, then we don't mark the request as pending. + var responseReceived = false; + + Action responseHandler = (response) => + { + responseReceived = true; + DownloadAsyncResponse(response, wwwRequest, originalUrl, callback); + }; + + var headers = GetCacheHeaders(originalUrl); + + if (headers == null) + { + requestWrapper = wwwRequest.SendGetRequest(coroutineHost, redirectedUrl, responseHandler); + } + else + { + requestWrapper = wwwRequest.SendPostRequest(coroutineHost, redirectedUrl, null, headers, responseHandler); + } + + if (!responseReceived) + { + pendingDownloadRequests.Add(requestWrapper); + } + } + + private void DownloadAsyncResponse(WWWResponse response, IWWWRequest wwwRequest, string originalUrl, Action callback) + { + pendingDownloadRequests.Remove(response.Request); + + var redirectUrl = GetRedirectUrl(response); + if (redirectUrl != null) + { + DownloadAsync(wwwRequest, originalUrl, redirectUrl, callback); + } + else + { + callback(response); + } + } + + private Dictionary GetCacheHeaders(string url) + { + CacheEntry entry; + if (metaDataCache.TryGet(GetAssetName(url), out entry)) + { + return new Dictionary + { + { "If-Modified-Since", entry.Modified }, + { "If-None-Match", entry.EntityTag }, + }; + } + + return null; + } + + private static string GetRedirectUrl(WWWResponse response) + { + string status = ""; + if (response.ResponseHeaders.TryGetValue("STATUS", out status)) + { + if (status.Contains("302")) + { + string location = ""; + if (response.ResponseHeaders.TryGetValue("LOCATION", out location)) + { + return location; + } + else + { + Debug.LogErrorFormat("Requested {0} and got a 302, but no location header", response.Url); + } + } + } + + return null; + } + } + + public class AssetBundlePersistenceStrategy : MachineCache.IPersistenceStrategy + { + public void WriteToCacheFile(string outputCacheFile, byte[] resource) + { + File.WriteAllBytes(outputCacheFile, resource); + } + + public AssetBundle ReadFromCacheFile(string inputCacheFile) + { + return AssetBundle.LoadFromFile(inputCacheFile); + } + } + + public class AssetMetadataPersistenceStrategy : MachineCache.IPersistenceStrategy + { + private static readonly XmlSerializer metaDataSerializer = new XmlSerializer(typeof(Container)); + + public CacheEntry ReadFromCacheFile(string filename) + { + using (var file = File.OpenRead(filename)) + { + var container = (Container) metaDataSerializer.Deserialize(file); + return container.CacheEntry; + } + } + + public void WriteToCacheFile(string filename, CacheEntry entry) + { + using (var file = File.CreateText(filename)) + { + var container = new Container + { + CacheEntry = entry + }; + metaDataSerializer.Serialize(file, container); + } + } + } + + public class CacheEntry + { + [XmlAttribute] public string EntityTag; + + [XmlAttribute] public DateTime LastFetched; + [XmlAttribute] public string Modified; + [XmlAttribute] public string Url; + } + + [XmlRoot("Cache")] + public class Container + { + [XmlElement] public CacheEntry CacheEntry; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetBundleDownloader.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetBundleDownloader.cs.meta new file mode 100644 index 0000000..0a06d94 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetBundleDownloader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 12cf77239e0cbef44950eec439e7fc87 +timeCreated: 1444833356 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetDatabaseStrategy.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetDatabaseStrategy.cs new file mode 100644 index 0000000..df97092 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetDatabaseStrategy.cs @@ -0,0 +1,10 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Assets +{ + public enum AssetDatabaseStrategy + { + Local, + Streaming + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetDatabaseStrategy.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetDatabaseStrategy.cs.meta new file mode 100644 index 0000000..acecf47 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/AssetDatabaseStrategy.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 48117de27aec83446a8ee791e0465765 +timeCreated: 1455879378 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/BehaviourWorkerCompatibilityCache.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/BehaviourWorkerCompatibilityCache.cs new file mode 100644 index 0000000..35b21a9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/BehaviourWorkerCompatibilityCache.cs @@ -0,0 +1,55 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.Linq; +using Improbable.Unity.Visualizer; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + class BehaviourWorkerCompatibilityCache + { + private readonly HashSet compatibleBehaviours; + + public BehaviourWorkerCompatibilityCache(WorkerPlatform platform) + { + compatibleBehaviours = new HashSet(); + var allTypes = AppDomain + .CurrentDomain + .GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()); + foreach (var type in allTypes) + { + var attributes = type.GetCustomAttributes(typeof(WorkerTypeAttribute), false); + if (typeof(MonoBehaviour).IsAssignableFrom(type)) + { + if (IsPlatformCompatible(attributes, platform)) + { + compatibleBehaviours.Add(type); + } + } + else if (attributes.Length > 0) + { + Debug.LogWarningFormat("{0} uses EngineTypeAttribute but is not MonoBehavoiur. The attribute will be ignored.", type.FullName); + } + } + } + + public bool IsCompatibleBehaviour(Type behaviourType) + { + return compatibleBehaviours.Contains(behaviourType); + } + + private static bool IsPlatformCompatible(object[] workerTypes, WorkerPlatform platform) + { + WorkerPlatform workerPlatformMask = 0; + for (int i = 0; i < workerTypes.Length; i++) + { + workerPlatformMask |= ((WorkerTypeAttribute) workerTypes[i]).WorkerPlatform; + } + + return workerTypes.Length == 0 || (workerPlatformMask & platform) != 0; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/BehaviourWorkerCompatibilityCache.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/BehaviourWorkerCompatibilityCache.cs.meta new file mode 100644 index 0000000..f8bd887 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/BehaviourWorkerCompatibilityCache.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5235339c146a675449098fb3ffe85a40 +timeCreated: 1448883129 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/CachingAssetDatabase.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/CachingAssetDatabase.cs new file mode 100644 index 0000000..265590f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/CachingAssetDatabase.cs @@ -0,0 +1,51 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Assets; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + public class CachingAssetDatabase : IAssetDatabase + { + private readonly IDictionary cachedGameObjects = new Dictionary(); + private readonly IAssetLoader gameObjectLoader; + + public CachingAssetDatabase(IAssetLoader gameObjectLoader) + { + this.gameObjectLoader = gameObjectLoader; + } + + public void LoadAsset(string prefabName, Action onAssetLoaded, Action onError) + { + GameObject cachedGameObject; + if (cachedGameObjects.TryGetValue(prefabName, out cachedGameObject)) + { + onAssetLoaded(cachedGameObject); + } + else + { + gameObjectLoader.LoadAsset(prefabName, gameObject => + { + if (!cachedGameObjects.ContainsKey(prefabName)) + { + cachedGameObjects.Add(prefabName, gameObject); + } + + onAssetLoaded(gameObject); + }, onError); + } + } + + public void CancelAllLoads() + { + gameObjectLoader.CancelAllLoads(); + } + + public bool TryGet(string prefabName, out GameObject prefabGameObject) + { + return cachedGameObjects.TryGetValue(prefabName, out prefabGameObject); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/CachingAssetDatabase.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/CachingAssetDatabase.cs.meta new file mode 100644 index 0000000..93873b3 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/CachingAssetDatabase.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8377b044344f0ba4797a82b5d61bacd8 +timeCreated: 1444833360 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/DefaultTemplateProvider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/DefaultTemplateProvider.cs new file mode 100644 index 0000000..9b6ae5f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/DefaultTemplateProvider.cs @@ -0,0 +1,197 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.IO; +using Improbable.Assets; +using Improbable.Unity.Assets; +using Improbable.Unity.Configuration; +using Improbable.Unity.Core; +using Improbable.Unity.Util; +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// The DefaultTemplateProvider switches between three strategies, based on whether it's running in the editor, is + /// configured to use local prefabs or has a streaming strategy set. + /// + public class DefaultTemplateProvider : MonoBehaviour, IEntityTemplateProvider + { + private Dictionary assetBundleNameToUrl; + private List pendingPrepareTemplates; + + // These can be overridden on the command line. + public bool UseLocalPrefabs; + public string LocalAssetDatabasePath = "../../build/assembly/"; + protected AssetDatabaseStrategy LoadingStrategy; + + // The template provider can't be instantiated during construction as Application.isEditor doesn't work. + private IEntityTemplateProvider templateProvider; + + private CloudAssemblyArtifactResolver pendingUrlResolveRequest = null; + + private IEntityTemplateProvider TemplateProvider + { + get + { + if (templateProvider == null) + { + var gameObjectLoader = InitializeAssetLoader(); + templateProvider = InitializeTemplateProvider(gameObjectLoader); + } + + return templateProvider; + } + } + + protected virtual IAssetLoader InitializeAssetLoader() + { + var projectName = SpatialOS.Configuration.ProjectName; + var deployment = SpatialOS.Deployment; + var assemblyName = deployment.HasValue ? deployment.Value.AssemblyName : SpatialOS.Configuration.AssemblyName; + + // If an assembly name is set, default to streaming from it. The strategy can still be overridden by the command line. + AssetDatabaseStrategy defaultStrategy = AssetDatabaseStrategy.Local; + if (!string.IsNullOrEmpty(projectName) && !string.IsNullOrEmpty(assemblyName)) + { + defaultStrategy = AssetDatabaseStrategy.Streaming; + } + + UseLocalPrefabs = SpatialOS.Configuration.GetCommandLineValue(CommandLineConfigNames.UseLocalPrefabs, UseLocalPrefabs); + LoadingStrategy = SpatialOS.Configuration.GetCommandLineValue(CommandLineConfigNames.AssetDatabaseStrategy, defaultStrategy); + LocalAssetDatabasePath = SpatialOS.Configuration.GetCommandLineValue(CommandLineConfigNames.LocalAssetDatabasePath, LocalAssetDatabasePath); + + IAssetLoader gameObjectLoader; + + if (Application.isEditor && UseLocalPrefabs) + { + gameObjectLoader = new PrefabGameObjectLoader(); + } + else + { + switch (LoadingStrategy) + { + case AssetDatabaseStrategy.Local: + var path = Path.GetFullPath(LocalAssetDatabasePath); + gameObjectLoader = new GameObjectFromAssetBundleLoader(new LocalAssetBundleLoader(path)); + break; + case AssetDatabaseStrategy.Streaming: + + pendingPrepareTemplates = new List(); + + var cachePath = Path.Combine(Application.persistentDataPath, "cache" + WorkerTypeUtils.ToWorkerName(SpatialOS.Configuration.WorkerPlatform)); + Directory.CreateDirectory(cachePath); + var assetBundleDownloader = new AssetBundleDownloader(cachePath, + new MachineCache(Path.Combine(cachePath, "assets"), new AssetBundlePersistenceStrategy()), + new MachineCache(Path.Combine(cachePath, "asset-metadata"), new AssetMetadataPersistenceStrategy()), + new WWWRequest(), + this); + assetBundleDownloader.GetAssetUrl = GetAssetUrl; + + var exponentialBackoffRetryAssetLoader = gameObject.GetComponent() + ?? gameObject.AddComponent(); + exponentialBackoffRetryAssetLoader.Init(assetBundleDownloader, + SpatialOS.Configuration.GetCommandLineValue(CommandLineConfigNames.MaxAssetLoadingRetries, -1), + SpatialOS.Configuration.GetCommandLineValue(CommandLineConfigNames.AssetLoadingRetryBackoffMilliseconds, -1)); + + gameObjectLoader = new GameObjectFromAssetBundleLoader(exponentialBackoffRetryAssetLoader); + pendingUrlResolveRequest = CloudAssemblyArtifactResolver.ResolveAssetUrls(this, new WWWRequest(), SpatialOS.Configuration.InfraServiceUrl, projectName, assemblyName, OnAssetBundleNameToUrlMapResolved, OnAssetResolveFailed); + break; + default: + throw new Exception(string.Format("Unknown loading strategy '{0}'", LoadingStrategy)); + } + } + + return gameObjectLoader; + } + + protected virtual IEntityTemplateProvider InitializeTemplateProvider(IAssetLoader gameObjectLoader) + { + return new AssetDatabaseTemplateProvider(new CachingAssetDatabase(new PreprocessingGameObjectLoader(gameObjectLoader))); + } + + /// + public virtual void PrepareTemplate(string prefabName, Action onSuccess, Action onError) + { + // TemplateProvider is initialized-on-access, so ensure we're all setup before checking pendingPrepareTemplates + var provider = TemplateProvider; + if (pendingPrepareTemplates != null) + { + pendingPrepareTemplates.Add(() => provider.PrepareTemplate(prefabName, onSuccess, onError)); + return; + } + + provider.PrepareTemplate(prefabName, onSuccess, onError); + } + + public void CancelAllTemplatePreparations() + { + if (pendingUrlResolveRequest != null) + { + pendingUrlResolveRequest.Cancel(); + pendingUrlResolveRequest = null; + } + + if (pendingPrepareTemplates != null) + { + pendingPrepareTemplates = null; + return; + } + + if (templateProvider != null) + { + templateProvider.CancelAllTemplatePreparations(); + } + } + + /// + public virtual GameObject GetEntityTemplate(string prefabName) + { + return TemplateProvider.GetEntityTemplate(prefabName); + } + + private string GetAssetUrl(string prefabName) + { + var assetBundleName = Platform.PrefabNameToAssetBundleName(prefabName.ToLowerInvariant()); + string url; + if (!assetBundleNameToUrl.TryGetValue(assetBundleName, out url)) + { + throw new KeyNotFoundException(string.Format("Trying to load a non-existent asset bundle named '{0}'", assetBundleName)); + } + + return url; + } + + private void OnAssetResolveFailed(Exception err) + { + pendingUrlResolveRequest = null; + + throw err; + } + + private void OnAssetBundleNameToUrlMapResolved(Dictionary map) + { + pendingUrlResolveRequest = null; + + assetBundleNameToUrl = map; + InvokePendingPrepareTemplates(); + } + + private void InvokePendingPrepareTemplates() + { + if (pendingPrepareTemplates == null) + { + return; + } + + // Start all pending PrepareTemplate requests now that we've finished resolving + for (var i = 0; i < pendingPrepareTemplates.Count; i++) + { + pendingPrepareTemplates[i](); + } + + pendingPrepareTemplates = null; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/DefaultTemplateProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/DefaultTemplateProvider.cs.meta new file mode 100644 index 0000000..3597bda --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/DefaultTemplateProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5d504af5aa1f0ed4fad51a0dfde196c0 +timeCreated: 1444833358 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/ExponentialBackoffRetryAssetLoader.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/ExponentialBackoffRetryAssetLoader.cs new file mode 100644 index 0000000..20c0ae2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/ExponentialBackoffRetryAssetLoader.cs @@ -0,0 +1,48 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Assets; +using Improbable.Unity.Util; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + class ExponentialBackoffRetryAssetLoader : MonoBehaviour, IAssetLoader + { + [Range(0, 100)] public int MaxRetries = 3; + + [Range(250, 10000)] public int StartBackoffTimeoutMilliseconds = 250; + + private IAssetLoader assetLoader; + + public void Init(IAssetLoader assetLoader, int maxRetries = -1, int startBackoffTimeoutMilliseconds = -1) + { + this.assetLoader = assetLoader; + MaxRetries = maxRetries != -1 ? maxRetries : MaxRetries; + StartBackoffTimeoutMilliseconds = startBackoffTimeoutMilliseconds != -1 ? startBackoffTimeoutMilliseconds : StartBackoffTimeoutMilliseconds; + } + + /// + public void LoadAsset(string prefabName, Action onAssetLoaded, Action onError) + { + var taskRunner = new TaskRunnerWithExponentialBackoff(); + Action runTask = () => { assetLoader.LoadAsset(prefabName, onAssetLoaded, taskRunner.ProcessResult); }; + Func evaluationFunc = (Exception e) => + { + return new TaskResult + { + IsSuccess = false, // This function should never be called if the load request was successful. + ErrorMessage = e.Message + }; + }; + Action onSuccess = (Exception e) => { throw new InvalidOperationException("ExponentialBackoffRetryAssetLoader: TaskRunnerWithExponentialBackoff::onSuccess was called. This code is not supposed to be reachable."); }; + Action onFailure = (string errorMessage) => { onError(new Exception(errorMessage)); }; + taskRunner.RunTaskWithRetries("ExponentialBackoffRetryAssetLoader::LoadAsset", this, runTask, evaluationFunc, onSuccess, onFailure, MaxRetries, StartBackoffTimeoutMilliseconds / 1000f); + } + + public void CancelAllLoads() + { + assetLoader.CancelAllLoads(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/ExponentialBackoffRetryAssetLoader.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/ExponentialBackoffRetryAssetLoader.cs.meta new file mode 100644 index 0000000..662fd11 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/ExponentialBackoffRetryAssetLoader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c35f15cdd70d86e4e925b26bc1b6c8bc +timeCreated: 1455725219 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/FilePersistenceStrategy.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/FilePersistenceStrategy.cs new file mode 100644 index 0000000..c0e4deb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/FilePersistenceStrategy.cs @@ -0,0 +1,19 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.IO; + +namespace Improbable.Assets +{ + public class FilePersistenceStrategy : MachineCache.IPersistenceStrategy + { + public void WriteToCacheFile(string outputCacheFile, byte[] resource) + { + File.WriteAllBytes(outputCacheFile, resource); + } + + public byte[] ReadFromCacheFile(string inputCacheFile) + { + return File.ReadAllBytes(inputCacheFile); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/FilePersistenceStrategy.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/FilePersistenceStrategy.cs.meta new file mode 100644 index 0000000..f4b3e70 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/FilePersistenceStrategy.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6124c94b427701f4fa3c0b6912bf798e +timeCreated: 1455879378 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/GameObjectFromAssetBundleLoader.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/GameObjectFromAssetBundleLoader.cs new file mode 100644 index 0000000..ccadc3e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/GameObjectFromAssetBundleLoader.cs @@ -0,0 +1,124 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Assets; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + class GameObjectFromAssetBundleLoader : IAssetLoader + { + private readonly IAssetLoader assetBundleLoader; + private HashSet inFlightAssetRequests = new HashSet(); + + //Each call to LoadAsset will have it's Actions stored in an OnLoadCallback object and called + //later, once the asset bundle has been loaded. + private struct OnLoadCallback + { + public Action onSuccess; + public Action onFail; + } + + //Stores a look up table of prefab names, to a list of call backs that are stored per call to LoadAsset + private Dictionary> mapPrefabToLoadCallbacks = new Dictionary>(); + + public GameObjectFromAssetBundleLoader(IAssetLoader assetBundleLoader) + { + this.assetBundleLoader = assetBundleLoader; + } + + public int NumberOfInFlightAssetRequests() + { + return inFlightAssetRequests.Count; + } + + public void LoadAsset(string prefabName, Action onGameObjectLoaded, Action onError) + { + var callBackPair = new OnLoadCallback { onSuccess = onGameObjectLoaded, onFail = onError }; + if (!mapPrefabToLoadCallbacks.ContainsKey(prefabName)) + { + mapPrefabToLoadCallbacks.Add(prefabName, new List { callBackPair }); + } + else + { + mapPrefabToLoadCallbacks[prefabName].Add(callBackPair); + } + + if (!inFlightAssetRequests.Contains(prefabName)) + { + inFlightAssetRequests.Add(prefabName); + assetBundleLoader.LoadAsset(prefabName, + loadedAssetBundle => OnAssetBundleLoaded(loadedAssetBundle, prefabName), + ex => OnAssetLoadFailure(prefabName, ex)); + } + } + + public void CancelAllLoads() + { + assetBundleLoader.CancelAllLoads(); + + mapPrefabToLoadCallbacks.Clear(); + + inFlightAssetRequests.Clear(); + } + + private void OnAssetBundleLoaded(AssetBundle loadedAssetBundle, string prefabName) + { + inFlightAssetRequests.Remove(prefabName); + + List listOfCallbacks; + if (prefabName != null && mapPrefabToLoadCallbacks.TryGetValue(prefabName, out listOfCallbacks)) + { + for (var i = 0; i < listOfCallbacks.Count; ++i) + { + try + { + var gameObject = loadedAssetBundle.LoadAsset(prefabName); + if (gameObject == null) + { + listOfCallbacks[i].onFail(new Exception(string.Format("Could not load the game object from asset '{0}'.", prefabName))); + } + else + { + listOfCallbacks[i].onSuccess(gameObject); + } + } + catch (Exception ex) + { + listOfCallbacks[i].onFail(ex); + } + } + + mapPrefabToLoadCallbacks.Remove(prefabName); + } + else + { + Debug.LogErrorFormat("Prefab {0} not found in asset load success callback", prefabName); + } + + //Always unload the asset bundle regardless + loadedAssetBundle.Unload(unloadAllLoadedObjects: false); + } + + private void OnAssetLoadFailure(string prefabName, Exception ex) + { + inFlightAssetRequests.Remove(prefabName); + + List listOfCallbacks; + if (mapPrefabToLoadCallbacks.TryGetValue(prefabName, out listOfCallbacks)) + { + for (var i = 0; i < listOfCallbacks.Count; ++i) + { + listOfCallbacks[i].onFail(ex); + } + + mapPrefabToLoadCallbacks.Remove(prefabName); + } + else + { + Debug.LogErrorFormat("Prefab {0} not found in asset load fail callback", prefabName); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/GameObjectFromAssetBundleLoader.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/GameObjectFromAssetBundleLoader.cs.meta new file mode 100644 index 0000000..edc4f44 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/GameObjectFromAssetBundleLoader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2df7a964d785f6e4fbc5ca00b2162274 +timeCreated: 1444833357 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetDatabase.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetDatabase.cs new file mode 100644 index 0000000..b2e6b21 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetDatabase.cs @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Assets +{ + /// + /// Maintains a cache of assets + /// + public interface IAssetDatabase : IAssetLoader + { + /// + /// Returns true and sets the prefab game object output parameter to a valid instance if the prefab has been + /// successfully loaded via . + /// If the prefab has not been loaded yet, then this method returns false and sets the prefab game object output + /// parameter to null. + /// + /// the name of the prefab to obtain + /// the prefab game object + /// true if successful, otherwise false + /// + /// Subsequent calls to this method will return the same prefab game object instance (due to caching). + /// You must clone the returned prefab game object to make your own instance. + /// + bool TryGet(string prefabName, out TGameObject prefabGameObject); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetDatabase.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetDatabase.cs.meta new file mode 100644 index 0000000..3f2a878 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetDatabase.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: cf79adea61d801943be0734f7a78b68c +timeCreated: 1455879378 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetLoader.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetLoader.cs new file mode 100644 index 0000000..582284a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetLoader.cs @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Assets +{ + public interface IAssetLoader + { + /// + /// Downloads an asset and invokes the callback when succeeded. + /// + /// the name of the prefab for which to load the asset. + /// called after the asset has been loaded. This callback is called in the calling thread. + /// called if for any reason the asset loading failed. This callback is called in the calling thread. + /// + /// This method is not blocking. + /// + void LoadAsset(string prefabName, Action onAssetLoaded, Action onError); + + /// + /// Cancels all ongoing asset loading requests. + /// + void CancelAllLoads(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetLoader.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetLoader.cs.meta new file mode 100644 index 0000000..507ec25 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IAssetLoader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5c58cfe5fec60e24db067a8fc407b3d5 +timeCreated: 1455879378 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IMachineCache.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IMachineCache.cs new file mode 100644 index 0000000..66b69e4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IMachineCache.cs @@ -0,0 +1,15 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Assets +{ + /// + /// Unit test wrapper for MachineCache + /// /// + /// + interface IMachineCache + { + bool TryAdd(string key, TIn cacheItem); + bool TryAddOrUpdate(string key, TIn cacheItem); + bool TryGet(string key, out TOut result); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IMachineCache.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IMachineCache.cs.meta new file mode 100644 index 0000000..f5843c7 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/IMachineCache.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ba3ab89976f1bf04088338a507fb19c9 +timeCreated: 1499176666 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/LocalAssetBundleLoader.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/LocalAssetBundleLoader.cs new file mode 100644 index 0000000..497ea59 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/LocalAssetBundleLoader.cs @@ -0,0 +1,63 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.IO; +using Improbable.Assets; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + /// + /// A filesystem-based asset bundle loader. + /// This implementation loads unity3d files from assets stored in the file strucuture required by the asset database. + /// + class LocalAssetBundleLoader : IAssetLoader + { + private const string entityPrefabSubdir = "unity"; + + private readonly string entityPrefabsPath; + + /// The directory where to find asset bundles. + public LocalAssetBundleLoader(string assetBundlesDir) + { + entityPrefabsPath = Path.Combine(assetBundlesDir, entityPrefabSubdir); + } + + public void LoadAsset(string prefabName, Action onAssetLoaded, Action onError) + { + var assetBundlePath = Path.Combine(entityPrefabsPath, Platform.PrefabNameToAssetBundleName(prefabName.ToLower())); + + try + { + if (!File.Exists(assetBundlePath)) + { + throw new IOException(string.Format("Failed to load prefab's '{0}' asset bundle from file '{1}'.\n", prefabName, assetBundlePath) + + "Asset is either missing or the local asset bundle path is incorrect."); + } + + var assetBundle = CreateAssetBundleFromFile(assetBundlePath); + if (assetBundle == null) + { + throw new Exception(string.Format("Failed to load prefab's '{0}' asset bundle from file '{1}'.\n", prefabName, assetBundlePath) + + "Asset is most likely corrupted."); + } + + onAssetLoaded(assetBundle); + } + catch (Exception e) + { + onError(e); + } + } + + public void CancelAllLoads() + { + // LoadAsset is instantaneous, so no need to do anything here. + } + + private static AssetBundle CreateAssetBundleFromFile(string assetBundlePath) + { + return AssetBundle.LoadFromFile(assetBundlePath); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/LocalAssetBundleLoader.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/LocalAssetBundleLoader.cs.meta new file mode 100644 index 0000000..27f6505 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/LocalAssetBundleLoader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a786f8fde2c7b0b47b96966044de60d2 +timeCreated: 1444833361 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/MachineCache.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/MachineCache.cs new file mode 100644 index 0000000..905f9d0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/MachineCache.cs @@ -0,0 +1,227 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.IO; +using System.Text; +using System.Threading; +using UnityEngine; + +namespace Improbable.Assets +{ + /// + /// A filesystem-based cache. It imports resources of one type and returns them in another. + /// This implementation attempts to use atomic file operations to ensure consistency. + /// + /// the type of resource to be stored in the cache (stored as a file). + /// the type of resource to be retrieved from the cache (mapped from the cached resource file). + /// + /// The implementation is heavily inspired by Nuget's repository cache which can be found here: + /// https://github.com/Haacked/NuGet/blob/9f25709fafb0d8e8fc2a3b34c1b77a1cb4b8a539/src/Core/Repositories/MachineCache.cs + /// + public class MachineCache : IMachineCache + { + private static readonly TimeSpan MutexWaitTime = TimeSpan.FromMinutes(3); + private readonly IPersistenceStrategy persistenceStrategy; + + private string CachePath { get; set; } + + public MachineCache(string cachePath, IPersistenceStrategy persistenceStrategy) + { + this.persistenceStrategy = persistenceStrategy; + CachePath = cachePath; + CreateCache(); + } + + /// + /// Attempt to Add an item to the cache. Will not update existing entries. + /// + /// the key used for later retrieval + /// the item to be cached + /// true if successful otherwise false + public bool TryAdd(string key, TIn cacheItem) + { + var path = GetFilePath(key); + if (!File.Exists(path)) + { + AtomicWriteToCache(cacheItem, path); + return true; + } + + return false; + } + + /// + /// Attempt to Add an item to the cache. Will update existing entries. + /// + /// the key used for later retrieval + /// the item to be cached + /// true if successful otherwise false + public bool TryAddOrUpdate(string key, TIn cacheItem) + { + try + { + var path = GetFilePath(key); + AtomicWriteToCache(cacheItem, path); + return true; + } + catch (Exception ex) + { + Debug.LogErrorFormat("Attempt to add or update an item to the cache failed.\n{0} = {1}\n{2}", key, cacheItem, ex); + return false; + } + } + + /// + /// Attempt to get an item from the cache + /// + /// the key of the item to retrieve + /// the item from the cache + /// true if successful otherwise false + public bool TryGet(string key, out TOut result) + { + try + { + var path = GetFilePath(key); + result = persistenceStrategy.ReadFromCacheFile(path); + return true; + } + catch (Exception) + { + result = default(TOut); + return false; + } + } + + private string GetFilePath(string key) + { + return Path.Combine(CachePath, Uri.EscapeDataString(key)); + } + + private void CreateCache() + { + if (!Directory.Exists(CachePath)) + { + Directory.CreateDirectory(CachePath); + } + } + + private void AtomicWriteToCache(TIn cacheItem, string path) + { + var tmp = Path.GetTempFileName(); + persistenceStrategy.WriteToCacheFile(tmp, cacheItem); + TryAct(() => + { + if (!File.Exists(path)) + { + File.Move(tmp, path); + } + else + { + var backup = Path.GetTempFileName(); + File.Replace(tmp, path, backup); + File.Delete(backup); + } + + return true; + }, path); + } + + private static string GenerateUniqueToken(string caseInsensitiveKey) + { + // We need something that is stable across all platforms and processes. + // Use an adaption of the mscorlib's string hash. + // http://referencesource.microsoft.com/#mscorlib/system/string.cs,827 + var pathBytes = Encoding.UTF8.GetBytes(caseInsensitiveKey.ToUpperInvariant()); + + // Disable overflow checking, we're not doing "real" math here. + unchecked + { + int hash1 = 5381; + int hash2 = hash1; + + for (int i = 0; i < pathBytes.Length; i++) + { + var c = pathBytes[i]; + hash1 = ((hash1 << 5) + hash1) ^ c; + if (i == pathBytes.Length - 1 || pathBytes[i + 1] == '\0') + { + break; + } + + hash2 = ((hash2 << 5) + hash2) ^ pathBytes[i + 1]; + } + + return (hash1 + (hash2 * 1566083941)).ToString(); + } + } + + /// + /// We use this method instead of the "safe" methods in FileSystem because it attempts to retry multiple times with + /// delays. + /// In our case, if we are unable to perform IO over the machine cache, we want to quit trying immediately. + /// + private static void TryAct(Func action, string path) + { + try + { + // Global: machine cache is per user across TS sessions + var mutexName = "Global\\" + GenerateUniqueToken(Path.GetFullPath(path)); + using (var mutex = new Mutex(false, mutexName)) + { + bool owner = false; + try + { + try + { + owner = mutex.WaitOne(MutexWaitTime); + // ideally we should throw an exception here if !owner such as + // throw new TimeoutException(string.Format("Timeout waiting for Machine Cache mutex for {0}", fullPath)); + // we decided against it: machine cache operations being "best effort" basis. + // this may cause "File in use" exceptions for long lasting operations such as downloading a large package on + // a slow network connection + } + catch (AbandonedMutexException) + { + // TODO: consider logging a warning; abandoning a mutex is an indication something wrong is going on + owner = true; // now mine + } + + action(); + } + finally + { + if (owner) + { + mutex.ReleaseMutex(); + } + } + } + } + catch (UnauthorizedAccessException) + { + // Do nothing if this fails. + } + } + + /// + /// The strategy that describes how the cache item is persisted and retrieved from disk + /// + public interface IPersistenceStrategy + { + /// + /// Called when the cache is persisting the item to disk + /// + /// the file to write to + /// + /// Note: outputCacheFile may be a temporary file and should not be used later for any reason. + void WriteToCacheFile(string outputCacheFile, TIn resource); + + /// + /// Load and deserialize a cache item + /// + /// the filename to read from + /// The cache item + TOut ReadFromCacheFile(string inputCacheFile); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/MachineCache.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/MachineCache.cs.meta new file mode 100644 index 0000000..eaa9830 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/MachineCache.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9e63d197aee438a46a68b2418e673945 +timeCreated: 1455879378 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/Platform.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/Platform.cs new file mode 100644 index 0000000..2fde9b2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/Platform.cs @@ -0,0 +1,94 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Assets +{ + /// + /// Platform provides methods to get prefab asset bundle suffixes for different platforms and build targets. + /// + public static class Platform + { + private static readonly string windowsAssetBundleSuffix = "windows"; + private static readonly string osxAssetBundleSuffix = "osx"; + private static readonly string iosAssetBundleSuffix = "ios"; + private static readonly string linuxAssetBundleSuffix = "linux"; + private static readonly string androidAssetBundleSuffix = "android"; + + private static readonly Dictionary runtimePlatformToAssetBundleSuffix = new Dictionary + { + { RuntimePlatform.WindowsEditor, windowsAssetBundleSuffix }, + { RuntimePlatform.WindowsPlayer, windowsAssetBundleSuffix }, + + { RuntimePlatform.OSXEditor, osxAssetBundleSuffix }, + { RuntimePlatform.OSXPlayer, osxAssetBundleSuffix }, + + { RuntimePlatform.LinuxPlayer, linuxAssetBundleSuffix }, + { RuntimePlatform.LinuxEditor, linuxAssetBundleSuffix }, + + { RuntimePlatform.IPhonePlayer, iosAssetBundleSuffix }, + + { RuntimePlatform.Android, androidAssetBundleSuffix } + }; + + /// + /// BuildPlatform is used instead of BuildTarget (which is defined only in UnityEditor). + /// + public enum BuildPlatform + { + Windows, + OSX, + Linux, + iOS, + Android + } + + private static readonly Dictionary buildPlatformToAssetBundleSuffix = new Dictionary + + { + { BuildPlatform.Windows, windowsAssetBundleSuffix }, + { BuildPlatform.OSX, osxAssetBundleSuffix }, + { BuildPlatform.Linux, linuxAssetBundleSuffix }, + { BuildPlatform.iOS, iosAssetBundleSuffix }, + { BuildPlatform.Android, androidAssetBundleSuffix }, + }; + + /// + /// Returns the asset bundle suffix for the given platform. + /// + public static string RuntimePlatformToAssetBundleSuffix(RuntimePlatform platform) + { + string suffix; + if (!runtimePlatformToAssetBundleSuffix.TryGetValue(platform, out suffix)) + { + throw new ArgumentException("No asset bundle name suffix exists for that platform."); + } + + return suffix; + } + + /// + /// Returns the asset bundle suffix for the given build target. + /// + public static string BuildPlatformToAssetBundleSuffix(BuildPlatform buildPlatform) + { + string suffix; + if (!buildPlatformToAssetBundleSuffix.TryGetValue(buildPlatform, out suffix)) + { + throw new ArgumentException("No asset bundle name suffix exists for that build platform."); + } + + return suffix; + } + + /// + /// Gets the asset bundle name for the given prefab name, by appending the correct suffix for the platform. + /// + public static string PrefabNameToAssetBundleName(string prefabName) + { + return String.Format("{0}@{1}", prefabName, RuntimePlatformToAssetBundleSuffix(Application.platform)); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/Platform.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/Platform.cs.meta new file mode 100644 index 0000000..562ca22 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/Platform.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 92ae7b4177ea7f342804c777d8a5e37f +timeCreated: 1480436804 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabCompiler.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabCompiler.cs new file mode 100644 index 0000000..a57f03c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabCompiler.cs @@ -0,0 +1,101 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using Improbable.Unity.Export; +using Improbable.Unity.Visualizer; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + public class PrefabCompiler + { + private readonly BehaviourWorkerCompatibilityCache compatibilityCache; + private readonly WorkerPlatform workerPlatform; + + public PrefabCompiler(WorkerPlatform workerPlatform) + { + this.workerPlatform = workerPlatform; + compatibilityCache = new BehaviourWorkerCompatibilityCache(workerPlatform); + } + + public void Compile(GameObject prefab) + { + CompileRecursively(prefab); + } + + private void CompileRecursively(GameObject prefab) + { + InvokePrefabExportProcessors(prefab); + DisableVisualizers(prefab); + DisableWrongPlatformMonoBehaviours(prefab); + ExportProcessChildren(prefab); + } + + private void DisableWrongPlatformMonoBehaviours(GameObject prefab) + { + var components = prefab.GetComponents(); + for (int i = 0; i < components.Length; i++) + { + if (components[i] != null && !compatibilityCache.IsCompatibleBehaviour(components[i].GetType())) + { + GameObject.DestroyImmediate(components[i], true); + } + } + } + + private void InvokePrefabExportProcessors(GameObject prefab) + { + var components = prefab.GetComponents(); + + foreach (var component in components) + { + if (component is IPrefabExportProcessor) + { + var processor = component as IPrefabExportProcessor; + processor.ExportProcess(workerPlatform); + + if (ShouldRemoveFromPrefab(processor)) + { + Object.DestroyImmediate(component, true); + } + } + } + } + + private static bool ShouldRemoveFromPrefab(object exportProcessor) + { + var attributes = exportProcessor.GetType().GetCustomAttributes(typeof(KeepOnExportedPrefabAttribute), true); + return attributes.Length == 0; + } + + private static void DisableVisualizers(GameObject prefab) + { + var components = prefab.GetComponents(); + + foreach (var component in components) + { + if (component == null) + { + continue; + } + + if (VisualizerMetadataLookup.Instance.IsVisualizer(component.GetType())) + { + component.enabled = false; + } + } + } + + private void ExportProcessChildren(GameObject prefab) + { + var prefabTransform = prefab.transform; + if (prefabTransform.childCount > 0) + { + foreach (Transform childTransform in prefabTransform) + { + CompileRecursively(childTransform.gameObject); + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabCompiler.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabCompiler.cs.meta new file mode 100644 index 0000000..918f2eb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabCompiler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9c5ad41057599e34ca564d5563d304ea +timeCreated: 1448883131 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabGameObjectLoader.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabGameObjectLoader.cs new file mode 100644 index 0000000..555b7eb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabGameObjectLoader.cs @@ -0,0 +1,45 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Linq; +using Improbable.Assets; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + public class PrefabGameObjectLoader : IAssetLoader + { + private const string AssetDatabaseTypeName = "Improbable.Unity.EditorTools.Assets.EditorPrefabGameObjectLoader"; + private IAssetLoader gameObjectLoader; + + public PrefabGameObjectLoader() + { + CreateEditorPrefabAssetDatabase(); + } + + private void CreateEditorPrefabAssetDatabase() + { + Type assetDatabaseType = (from assembly in AppDomain.CurrentDomain.GetAssemblies() + from type in assembly.GetTypes() + where type.FullName == AssetDatabaseTypeName + select type).First(); + + if (assetDatabaseType == null) + { + throw new Exception(String.Format("Could not find required asset database type {0}", AssetDatabaseTypeName)); + } + + gameObjectLoader = Activator.CreateInstance(assetDatabaseType) as IAssetLoader; + } + + public void LoadAsset(string prefabName, Action onGameObjectLoaded, Action onError) + { + gameObjectLoader.LoadAsset(prefabName, onGameObjectLoaded, onError); + } + + public void CancelAllLoads() + { + gameObjectLoader.CancelAllLoads(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabGameObjectLoader.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabGameObjectLoader.cs.meta new file mode 100644 index 0000000..6e7cd2c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PrefabGameObjectLoader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 501c1eb1107d62a4cb285eebca4107da +timeCreated: 1444833358 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PreprocessingGameObjectLoader.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PreprocessingGameObjectLoader.cs new file mode 100644 index 0000000..59cb641 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PreprocessingGameObjectLoader.cs @@ -0,0 +1,35 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Assets; +using Improbable.Unity.Core; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + public class PreprocessingGameObjectLoader : IAssetLoader + { + private readonly IAssetLoader gameObjectLoader; + private readonly PrefabCompiler prefabCompiler; + + public PreprocessingGameObjectLoader(IAssetLoader gameObjectLoader) + { + this.gameObjectLoader = gameObjectLoader; + this.prefabCompiler = new PrefabCompiler(SpatialOS.Configuration.WorkerPlatform); + } + + public void LoadAsset(string prefabName, Action onAssetLoaded, Action onError) + { + gameObjectLoader.LoadAsset(prefabName, gameObject => + { + prefabCompiler.Compile(gameObject); + onAssetLoaded(gameObject); + }, onError); + } + + public void CancelAllLoads() + { + gameObjectLoader.CancelAllLoads(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PreprocessingGameObjectLoader.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PreprocessingGameObjectLoader.cs.meta new file mode 100644 index 0000000..5d38144 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Assets/PreprocessingGameObjectLoader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a295953df62a78846b170ab507f10247 +timeCreated: 1444833360 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration.meta new file mode 100644 index 0000000..154c483 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9e1d420bfa2a1d34587e0a4434624949 +folderAsset: yes +timeCreated: 1483051661 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/EditorCompatibilityExtensions.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/EditorCompatibilityExtensions.cs new file mode 100644 index 0000000..a2957f7 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/EditorCompatibilityExtensions.cs @@ -0,0 +1,57 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; +using UnityEngine; + +namespace Improbable.Unity.CodeGeneration +{ + /// + /// Extension methods on built in schema types to convert them to Unity native types + /// so they can be displayed properly in the editor. + /// + public static class EditorCompatibilityExtensions + { + /// + /// Converts to for Unity editor compatibility. + /// + public static long AsUnityType(this EntityId entityId) + { + return entityId.Id; + } + + /// + /// Converts to for Unity editor compatibility. + /// + public static Vector3 AsUnityType(this Vector3d vector) + { + return vector.ToUnityVector(); + } + + /// + /// Converts to for Unity editor compatibility. + /// + public static Vector3 AsUnityType(this Vector3f vector) + { + return vector.ToUnityVector(); + } + + /// + /// Converts to for Unity editor compatibility. + /// + public static Vector3 AsUnityType(this Coordinates coordinates) + { + return new Vector3((float) coordinates.x, (float) coordinates.y, (float) coordinates.z); + } + + public static object AsSpatialType(this long longValue) + { + if (typeof(TSpatial) == typeof(EntityId)) + { + return new EntityId(longValue); + } + + throw new ArgumentOutOfRangeException(string.Format("Cannot convert long to type {0}.", typeof(TSpatial))); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/EditorCompatibilityExtensions.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/EditorCompatibilityExtensions.cs.meta new file mode 100644 index 0000000..26b0724 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/EditorCompatibilityExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 36fc768ccbdd2b24aa28cdbfcd632b56 +timeCreated: 1485362621 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponent.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponent.cs new file mode 100644 index 0000000..83cefdc --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponent.cs @@ -0,0 +1,38 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Worker; + +namespace Improbable.Unity.CodeGeneration +{ + /// + /// Unity companion component to SpatialOS component. + /// + public interface ISpatialOsComponent + { + /// + /// Gets the component ID of this component. + /// + uint ComponentId { get; } + + /// + /// Returns whether or not we have authority on this component. + /// + [System.Obsolete("Please use \"Authority == Improbable.Worker.Authority.Authoritative || Authority == Improbable.Worker.Authority.AuthorityLossImminent\".")] + bool HasAuthority { get; } + + /// + /// Returns the state of authority of this component. + /// + Authority Authority { get; } + + /// + /// Returns the entity ID of this component. + /// + EntityId EntityId { get; } + + /// + /// Returns whether or not the component has received its first set of values and is listening for updates. + /// + bool IsComponentReady { get; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponent.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponent.cs.meta new file mode 100644 index 0000000..8230452 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponent.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5dfffc727a36153448317931cfb1d062 +timeCreated: 1490370009 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponentInternal.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponentInternal.cs new file mode 100644 index 0000000..1219324 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponentInternal.cs @@ -0,0 +1,31 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Core; +using Improbable.Unity.Entity; +using Improbable.Worker; + +namespace Improbable.Unity.CodeGeneration +{ + public interface ISpatialOsComponentInternal : ISpatialOsComponent, IPipelineEntityComponentOpsReceiver + { + /// + /// Initializes the SpatialOsComponent + /// + bool Init(ISpatialCommunicator communicator, IEntityObject entityObject); + + /// + /// Invoked when authority changes for this component. + /// + event OnAuthorityChangeCallback OnAuthorityChange; + } + + /// + /// The type of callback to listen for authority changes. + /// + public delegate void OnAuthorityChangeCallback(Authority newAuthority); + + /// + /// The type of callback to listen for component ready changes. + /// + public delegate void OnComponentReadyCallback(); +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponentInternal.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponentInternal.cs.meta new file mode 100644 index 0000000..70cc926 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/ISpatialOsComponentInternal.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 25ca491dfc7d9184c9de9c7e4428701a +timeCreated: 1493119118 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentBase.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentBase.cs new file mode 100644 index 0000000..b98ea42 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentBase.cs @@ -0,0 +1,282 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Core; +using Improbable.Unity.Entity; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.CodeGeneration +{ + /// + /// The base class for all generated Spatial OS Component MonoBehaviours, which + /// implements all shared component logic. + /// + public abstract class SpatialOsComponentBase : MonoBehaviour, ICanAttachEditorDataObject, ISpatialOsComponentInternal, IDisposable + { + /// + public abstract uint ComponentId { get; } + + /// + [Obsolete("Please use \"Authority == Improbable.Worker.Authority.Authoritative || Authority == Improbable.Worker.Authority.AuthorityLossImminent\".")] + public bool HasAuthority + { + get { return authority == Authority.Authoritative || authority == Authority.AuthorityLossImminent; } + } + + /// + public Authority Authority + { + get { return authority; } + } + + /// + public EntityId EntityId + { + get { return entityId; } + } + + /// + public bool IsComponentReady + { + get { return isComponentReady; } + } + + /// + /// Exposes methods for sending commands from this component. + /// + public IComponentCommander SendCommand + { + get { return commander ?? (commander = new ComponentCommander(this, communicator)); } + } + + protected Authority authority; + protected bool isComponentReady; + + private List dispatcherCallbackKeys; + + protected ISpatialCommunicator communicator; + protected IComponentCommander commander; + protected EntityId entityId; + protected IEntityObject entityObject; + protected List onAuthorityChangeCallbacks; + protected List onComponentReadyCallbacks; + + #region Editor-only members + +#if UNITY_EDITOR + private IComponentEditorDataObject editorDataObject; +#endif + + #endregion + + protected List DispatcherCallbackKeys + { + get { return dispatcherCallbackKeys ?? (dispatcherCallbackKeys = new List()); } + } + + /// + /// This is an implementation detail; it should not be called by user code. + /// + public virtual bool Init(ISpatialCommunicator communicator, IEntityObject entityObject) + { + if (this.communicator != null) + { + return false; + } + + this.communicator = communicator; + this.entityObject = entityObject; + this.entityId = entityObject.EntityId; + + entityObject.Components.RegisterInterestedComponent(ComponentId, this); + return true; + } + + public virtual void Dispose() + { + if (commander != null) + { + commander.Dispose(); + commander = null; + } + + if (dispatcherCallbackKeys != null) + { + for (var i = 0; i < dispatcherCallbackKeys.Count; i++) + { + communicator.Remove(dispatcherCallbackKeys[i]); + } + + dispatcherCallbackKeys.Clear(); + } + + entityObject.Components.DeregisterInterestedComponent(ComponentId); + } + + /// + public abstract void OnAddComponentPipelineOp(AddComponentPipelineOp op); + + /// + public void OnRemoveComponentPipelineOp(RemoveComponentPipelineOp op) + { + OnRemoveComponentDispatcherCallback(new RemoveComponentOp { EntityId = entityId }); + } + + /// + public abstract void OnComponentUpdatePipelineOp(UpdateComponentPipelineOp op); + + /// + public void OnAuthorityChangePipelineOp(ChangeAuthorityPipelineOp op) + { + OnAuthorityChangeDispatcherCallback(new AuthorityChangeOp { EntityId = entityId, Authority = op.Authority }); + } + + protected void OnRemoveComponentDispatcherCallback(RemoveComponentOp op) + { + if (op.EntityId != entityId) + { + return; + } + + isComponentReady = false; + Dispose(); + } + + /// + /// Invoked when authority changes for this component. + /// + public event OnAuthorityChangeCallback OnAuthorityChange + { + add + { + if (onAuthorityChangeCallbacks == null) + { + onAuthorityChangeCallbacks = new List(); + } + + onAuthorityChangeCallbacks.Add(value); + } + remove + { + if (onAuthorityChangeCallbacks != null) + { + onAuthorityChangeCallbacks.Remove(value); + } + } + } + + protected internal void OnAuthorityChangeDispatcherCallback(global::Improbable.Worker.AuthorityChangeOp op) + { + if (op.EntityId != entityId) + { + return; + } + + authority = op.Authority; + + if (onAuthorityChangeCallbacks == null) + { + return; + } + + for (var i = 0; i < onAuthorityChangeCallbacks.Count; i++) + { + try + { + onAuthorityChangeCallbacks[i](op.Authority); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + /// + /// Invoked when this component is ready and listening for updates. + /// + public event OnComponentReadyCallback OnComponentReady + { + add + { + if (isComponentReady) + { + value(); + return; + } + + if (onComponentReadyCallbacks == null) + { + onComponentReadyCallbacks = new List(); + } + + onComponentReadyCallbacks.Add(value); + } + remove + { + if (onComponentReadyCallbacks != null) + { + onComponentReadyCallbacks.Remove(value); + } + } + } + + #region Editor methods. + + /// + public void AttachEditorDataObject(IComponentEditorDataObject editorDataObject) + { +#if UNITY_EDITOR + this.editorDataObject = editorDataObject; +#endif + } + + /// + public void RemoveEditorDataObject() + { +#if UNITY_EDITOR + this.editorDataObject = null; +#endif + } + + #region Editor-only methods + +#if UNITY_EDITOR + protected void LogComponentUpdate(string componentName, object componentValue) + { + if (editorDataObject == null) + { + return; + } + + editorDataObject.LogComponentUpdate(componentName, componentValue); + } + + protected void LogCommandRequest(DateTime dateTime, string commandName, object payload) + { + if (editorDataObject == null) + { + return; + } + + editorDataObject.LogCommandRequest(dateTime, commandName, payload); + } + + protected void FinalizeComponentUpdateLog() + { + if (editorDataObject == null) + { + return; + } + + editorDataObject.SendUpdateLog(); + } +#endif + + #endregion + + #endregion + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentBase.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentBase.cs.meta new file mode 100644 index 0000000..de9f00b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentBase.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 681b3e330237d434ca857e6cbd895117 +timeCreated: 1482423031 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentEditorBase.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentEditorBase.cs new file mode 100644 index 0000000..a9c0c37 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentEditorBase.cs @@ -0,0 +1,37 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Worker; + +namespace Improbable.Unity.CodeGeneration +{ + /// + /// Base ComponentEditorDataObject class for SpatialOS generated components. + /// + public abstract class SpatialOsComponentEditorBase : ComponentEditorDataObject + where TComponent : class, ISpatialOsComponent, ICanAttachEditorDataObject + { + /// + /// Returns whether or not we have authority on this component. + /// + public Authority Authority + { + get { return Component.Authority; } + } + + /// + /// Returns whether or not the component has received its first set of values and is listening for updates. + /// + public bool IsComponentReady + { + get { return Component.IsComponentReady; } + } + + /// + /// Returns the entity ID of the component. + /// + public EntityId EntityId + { + get { return Component.EntityId; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentEditorBase.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentEditorBase.cs.meta new file mode 100644 index 0000000..3c4bf76 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/CodeGeneration/SpatialOsComponentEditorBase.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ef6dbe4bbe79e5a42b2ecf2c5f3261cf +timeCreated: 1490624831 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands.meta new file mode 100644 index 0000000..c9d43c6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 902cc9934ad28344081dc0bc6bb668ec +folderAsset: yes +timeCreated: 1479902249 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandDelivery.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandDelivery.cs new file mode 100644 index 0000000..0eb6978 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandDelivery.cs @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// Setting for delivering entity commands. + /// + public enum CommandDelivery + { + // Always send commands over the network. A successful command response guarantees that the command was fully executed on the target worker. + RoundTrip = 0, + + // Do not send commands over the network if the target entity is on the same worker as the command originator and the worker is authoritative over the target entity's component. No guarantees. + ShortCircuit = 1 + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandDelivery.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandDelivery.cs.meta new file mode 100644 index 0000000..f046990 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandDelivery.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a01758ebe4aea344d945996f21f9d6b7 +timeCreated: 1499344693 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandResponseHandler.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandResponseHandler.cs new file mode 100644 index 0000000..c195069 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandResponseHandler.cs @@ -0,0 +1,82 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + class CommandResponseHandler : ICommandResponseHandler + { + private readonly Queue> successCallbacks = new Queue>(); + private readonly Queue failureCallbacks = new Queue(); + + /// + /// Utility function for wrapping callbacks into CommandResponseHandler objects. + /// + public static CommandResponseHandler Wrap(Action> wrap) + { + var commandResponseHandler = new CommandResponseHandler(); + wrap(commandResponseHandler.Trigger); + return commandResponseHandler; + } + + /// + public ICommandResponseHandler OnSuccess(CommandSuccessCallback successCallback) + { + this.successCallbacks.Enqueue(successCallback); + return this; + } + + /// + public ICommandResponseHandler OnFailure(CommandFailureCallback failureCallback) + { + this.failureCallbacks.Enqueue(failureCallback); + return this; + } + + public void Trigger(ICommandCallbackResponse response) + { + if (response.StatusCode == StatusCode.Success) + { + TriggerSuccess(response); + } + else + { + TriggerFailure(response); + } + } + + private void TriggerSuccess(ICommandCallbackResponse response) + { + while (successCallbacks.Count > 0) + { + try + { + successCallbacks.Dequeue()(response.Response.Value); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + private void TriggerFailure(ICommandCallbackResponse response) + { + while (failureCallbacks.Count > 0) + { + try + { + failureCallbacks.Dequeue()(response); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandResponseHandler.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandResponseHandler.cs.meta new file mode 100644 index 0000000..a939c1a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/CommandResponseHandler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4c1c9cb9877bcdd4b92422dfa19f242f +timeCreated: 1485452592 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/Commander.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/Commander.cs new file mode 100644 index 0000000..6356b64 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/Commander.cs @@ -0,0 +1,379 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Collections; +using Improbable.Entity.Component; +using Improbable.Unity.Core.EntityQueries; +using Improbable.Worker; +using Improbable.Worker.Query; + +namespace Improbable.Unity.Core +{ + class Commander : ICommandSender, IWorkerCommandSender, IDisposable + { + private struct EntityComponentId + { + private readonly EntityId entityId; + private readonly uint componentId; + + public EntityComponentId(EntityId entityId, uint componentId) + { + this.entityId = entityId; + this.componentId = componentId; + } + + public override bool Equals(object obj) + { + if (!(obj is EntityComponentId)) + { + return false; + } + + var other = (EntityComponentId) obj; + return other.entityId == entityId && other.componentId == componentId; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } + + private readonly Dictionary> componentToRequestIds; + private readonly ComponentCommander componentCommander; + private readonly ISpatialCommunicator communicator; + + private const bool PerformAuthorityCheck = true; + private const bool SkipAuthorityCheck = false; + + public Commander(ComponentCommander componentCommander, ISpatialCommunicator communicator) + { + this.componentCommander = componentCommander; + this.communicator = communicator; + + componentToRequestIds = new Dictionary>(); + + communicator.ComponentAuthorityChanged += OnComponentAuthorityChanged; + } + + public void Dispose() + { + communicator.ComponentAuthorityChanged -= OnComponentAuthorityChanged; + } + + private void OnComponentAuthorityChanged(EntityId entityId, IComponentMetaclass componentId, Authority authority, object componentObj) + { + if (authority == Authority.Authoritative) + { + return; + } + + var component = new EntityComponentId(entityId, componentId.ComponentId); + HashSet requestIds; + if (!componentToRequestIds.TryGetValue(component, out requestIds)) + { + return; + } + + foreach (var requestId in requestIds) + { + componentCommander.ForgetRequestId(requestId); + } + + componentToRequestIds.Remove(component); + } + + private void SendGenericCommand(IComponentWriter writer, bool requireAuthority, CommandCallback callback, + Action sendAction) + { + var callbackWrapper = new CommandCallbackWrapper(callback); + if (requireAuthority && (writer == null || communicator.GetAuthority(writer.EntityId, writer.ComponentId) == Authority.NotAuthoritative)) + { + // This needs to be deferred, so that all callbacks are registered + // before they are actually called. + communicator.Defer(() => callbackWrapper.TriggerWithAuthorityError()); + return; + } + + sendAction(); + } + + public void SendCommand(IComponentWriter writer, + ICommandDescriptor commandDescriptor, TRequest request, + EntityId entityId, CommandCallback callback, TimeSpan? timeout = null, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) + where TCommand : ICommandMetaclass, new() + { + SendCommandInternal(writer, PerformAuthorityCheck, commandDescriptor, request, entityId, callback, timeout, commandDelivery); + } + + public void SendCommand( + ICommandDescriptor commandDescriptor, TRequest request, + EntityId entityId, CommandCallback callback, TimeSpan? timeout = null, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) + where TCommand : ICommandMetaclass, new() + { + SendCommandInternal(null, SkipAuthorityCheck, commandDescriptor, request, entityId, callback, timeout, commandDelivery); + } + + private void SendCommandInternal(IComponentWriter writer, bool requireAuthority, + ICommandDescriptor commandDescriptor, TRequest request, + EntityId entityId, CommandCallback callback, TimeSpan? timeout, CommandDelivery commandDelivery) + where TCommand : ICommandMetaclass, new() + { + Action sendAction = () => + { + var rawRequest = commandDescriptor.CreateRequest(request); + Func, TResponse> extractResponse = + rawResponse => ExtractResponse(commandDescriptor, rawResponse); + var requestId = componentCommander.SendCommandInternal(entityId, rawRequest, extractResponse, callback, timeout, commandDelivery); + TrackRequest(writer, requestId); + }; + SendGenericCommand(writer, requireAuthority, callback, sendAction); + } + + public static TResponse ExtractResponse + (ICommandDescriptor commandDescriptor, ICommandResponse rawResponse) + where TCommand : ICommandMetaclass, new() + { + return commandDescriptor.ExtractResponse(rawResponse); + } + + public void ReserveEntityId(IComponentWriter writer, CommandCallback callback, + TimeSpan? timeout = null) + { + ReserveEntityIdInternal(writer, PerformAuthorityCheck, callback, timeout); + } + + public void ReserveEntityId(CommandCallback callback, TimeSpan? timeout = null) + { + ReserveEntityIdInternal(null, SkipAuthorityCheck, callback, timeout); + } + + private void ReserveEntityIdInternal(IComponentWriter writer, bool requireAuthority, CommandCallback callback, + TimeSpan? timeout = null) + { + Action sendAction = () => + { + var requestId = componentCommander.ReserveEntityIdInternal(callback, timeout); + TrackRequest(writer, requestId); + }; + SendGenericCommand(writer, requireAuthority, callback, sendAction); + } + + public void ReserveEntityIds(IComponentWriter writer, CommandCallback callback, + uint numberOfEntityIds, TimeSpan? timeout = null) + { + ReserveEntityIdsInternal(writer, PerformAuthorityCheck, callback, numberOfEntityIds, timeout); + } + + public void ReserveEntityIds(CommandCallback callback, uint numberOfEntityIds, TimeSpan? timeout = null) + { + ReserveEntityIdsInternal(null, SkipAuthorityCheck, callback, numberOfEntityIds, timeout); + } + + private void ReserveEntityIdsInternal(IComponentWriter writer, bool requireAuthority, CommandCallback callback, + uint numberOfEntityIds, TimeSpan? timeout = null) + { + Action sendAction = () => + { + var requestId = componentCommander.ReserveEntityIdsInternal(callback, numberOfEntityIds, timeout); + TrackRequest(writer, requestId); + }; + SendGenericCommand(writer, requireAuthority, callback, sendAction); + } + + public void CreateEntity(IComponentWriter writer, EntityId reservedEntityId, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null) + { + CreateEntityInternal(writer, PerformAuthorityCheck, reservedEntityId, template, callback, timeout); + } + + public void CreateEntity(EntityId reservedEntityId, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null) + { + CreateEntityInternal(null, SkipAuthorityCheck, reservedEntityId, template, callback, timeout); + } + + public void CreateEntity(IComponentWriter writer, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null) + { + CreateEntityInternal(writer, PerformAuthorityCheck, template, callback, timeout); + } + + public void CreateEntity(Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null) + { + CreateEntityInternal(null, SkipAuthorityCheck, template, callback, timeout); + } + + private void CreateEntityInternal(IComponentWriter writer, bool requireAuthority, EntityId reservedEntityId, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null) + { + Action sendAction = () => + { + var requestId = componentCommander.CreateEntityInternal(reservedEntityId, template, callback, timeout); + TrackRequest(writer, requestId); + }; + SendGenericCommand(writer, requireAuthority, callback, sendAction); + } + + private void CreateEntityInternal(IComponentWriter writer, bool requireAuthority, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null) + { + Action sendAction = () => + { + var requestId = componentCommander.CreateEntityInternal(template, callback, timeout); + TrackRequest(writer, requestId); + }; + SendGenericCommand(writer, requireAuthority, callback, sendAction); + } + + public void DeleteEntity(IComponentWriter writer, EntityId entityId, + CommandCallback callback, TimeSpan? timeout = null) + { + DeleteEntityInternal(writer, PerformAuthorityCheck, entityId, callback, timeout); + } + + public void DeleteEntity(EntityId entityId, + CommandCallback callback, TimeSpan? timeout = null) + { + DeleteEntityInternal(null, SkipAuthorityCheck, entityId, callback, timeout); + } + + + private void DeleteEntityInternal(IComponentWriter writer, bool requireAuthority, EntityId entityId, + CommandCallback callback, TimeSpan? timeout = null) + { + Action sendAction = () => + { + var requestId = componentCommander.DeleteEntityInternal(entityId, callback, timeout); + TrackRequest(writer, requestId); + }; + SendGenericCommand(writer, requireAuthority, callback, sendAction); + } + + public void SendQuery(IComponentWriter writer, EntityQuery query, + CommandCallback callback, TimeSpan? timeout = null) + { + SendQueryInternal(writer, PerformAuthorityCheck, query, callback, timeout); + } + + public void SendQuery(EntityQuery query, + CommandCallback callback, TimeSpan? timeout = null) + { + SendQueryInternal(null, SkipAuthorityCheck, query, callback, timeout); + } + + #region ICommandResponseHandler boilerplate + + /// + public ICommandResponseHandler SendCommand(ICommandDescriptor commandDescriptor, + TRequest request, EntityId entityId, TimeSpan? timeout = null, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) where TCommand : ICommandMetaclass, new() + { + return CommandResponseHandler.Wrap(callback => SendCommandInternal(null, SkipAuthorityCheck, commandDescriptor, request, entityId, callback, timeout, commandDelivery)); + } + + /// + public ICommandResponseHandler SendCommand(IComponentWriter writer, ICommandDescriptor commandDescriptor, + TRequest request, EntityId entityId, TimeSpan? timeout, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) where TCommand : ICommandMetaclass, new() + { + return CommandResponseHandler.Wrap(callback => SendCommandInternal(writer, PerformAuthorityCheck, commandDescriptor, request, entityId, callback, timeout, commandDelivery)); + } + + public ICommandResponseHandler ReserveEntityId(TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => ReserveEntityId(callback, timeout)); + } + + public ICommandResponseHandler ReserveEntityIds(uint numberOfEntityIds, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => ReserveEntityIds(callback, numberOfEntityIds, timeout)); + } + + public ICommandResponseHandler CreateEntity(EntityId reservedEntityId, Worker.Entity template, + TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => CreateEntity(reservedEntityId, template, callback, timeout)); + } + + public ICommandResponseHandler CreateEntity(Worker.Entity template, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => CreateEntity(template, callback, timeout)); + } + + public ICommandResponseHandler SendQuery(EntityQuery query, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => SendQuery(query, callback, timeout)); + } + + public ICommandResponseHandler DeleteEntity(EntityId entityId, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => DeleteEntity(entityId, callback, timeout)); + } + + public ICommandResponseHandler ReserveEntityId(IComponentWriter writer, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => ReserveEntityId(writer, callback, timeout)); + } + + public ICommandResponseHandler ReserveEntityIds(IComponentWriter writer, uint numberOfEntityIds, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => ReserveEntityIds(writer, callback, numberOfEntityIds, timeout)); + } + + public ICommandResponseHandler CreateEntity(IComponentWriter writer, EntityId reservedEntityId, + Worker.Entity template, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => CreateEntity(writer, reservedEntityId, template, callback, timeout)); + } + + public ICommandResponseHandler CreateEntity(IComponentWriter writer, Worker.Entity template, + TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => CreateEntity(writer, template, callback, timeout)); + } + + public ICommandResponseHandler DeleteEntity(IComponentWriter writer, EntityId entityId, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => DeleteEntity(writer, entityId, callback, timeout)); + } + + public ICommandResponseHandler SendQuery(IComponentWriter writer, EntityQuery query, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => SendQuery(writer, query, callback, timeout)); + } + + #endregion + + private void SendQueryInternal(IComponentWriter writer, bool requireAuthority, EntityQuery query, + CommandCallback callback, TimeSpan? timeout = null) + { + Action sendAction = () => + { + var requestId = componentCommander.SendQueryInternal(query, callback, timeout); + TrackRequest(writer, requestId); + }; + SendGenericCommand(writer, requireAuthority, callback, sendAction); + } + + private void TrackRequest(IComponentWriter writer, Option requestId) + { + if (writer == null || !requestId.HasValue) + { + return; + } + + var component = new EntityComponentId(writer.EntityId, writer.ComponentId); + HashSet requestIds; + if (!componentToRequestIds.TryGetValue(component, out requestIds)) + { + requestIds = new HashSet { requestId.Value }; + componentToRequestIds.Add(component, requestIds); + } + else + { + requestIds.Add(requestId.Value); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/Commander.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/Commander.cs.meta new file mode 100644 index 0000000..0df4e60 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/Commander.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 61a9e71c16f95584ba9b57e1ce175bd2 +timeCreated: 1483529603 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ComponentCommander.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ComponentCommander.cs new file mode 100644 index 0000000..68f5e92 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ComponentCommander.cs @@ -0,0 +1,308 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Collections; +using Improbable.Unity.CodeGeneration; +using Improbable.Unity.Core.EntityQueries; +using Improbable.Worker; +using Improbable.Worker.Query; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + class ComponentCommander : IComponentCommander + { + private const uint DefaultCommandTimeoutMs = 1000; + + private readonly HashSet commandResponseThunksRegistered; + private readonly Dictionary requestIdToCallback; + + private readonly ISpatialOsComponentInternal component; + private readonly Collections.List dispatcherCallbackKeys; + + private readonly ISpatialCommunicator communicator; + + public ComponentCommander(ISpatialOsComponentInternal component, ISpatialCommunicator communicator) + { + commandResponseThunksRegistered = new HashSet(); + requestIdToCallback = new Dictionary(); + + this.component = component; + this.communicator = communicator; + + dispatcherCallbackKeys = new Collections.List(); + dispatcherCallbackKeys.Add( + communicator.RegisterReserveEntityIdResponse(CreateOnResponseThunk( + op => op.RequestId.Id, op => op.StatusCode, op => op.Message, op => op.EntityId.HasValue ? new ReserveEntityIdResult(op.EntityId.Value) : new Option()))); + dispatcherCallbackKeys.Add( + communicator.RegisterCreateEntityResponse(CreateOnResponseThunk( + op => op.RequestId.Id, op => op.StatusCode, op => op.Message, op => op.EntityId.HasValue ? new CreateEntityResult(op.EntityId.Value) : new Option()))); + dispatcherCallbackKeys.Add( + communicator.RegisterReserveEntityIdsResponse(CreateOnResponseThunk( + op => op.RequestId.Id, op => op.StatusCode, op => op.Message, op => new ReserveEntityIdsResult(op.FirstEntityId.Value, op.NumberOfEntityIds)))); + dispatcherCallbackKeys.Add( + communicator.RegisterDeleteEntityResponse(CreateOnResponseThunk( + op => op.RequestId.Id, op => op.StatusCode, op => op.Message, op => new DeleteEntityResult(op.EntityId)))); + dispatcherCallbackKeys.Add( + communicator.RegisterEntityQueryResponse(CreateOnResponseThunk( + op => op.RequestId.Id, op => op.StatusCode, op => op.Message, op => new EntityQueryResult(op.ResultCount, op.Result)))); + + if (component != null) + { + component.OnAuthorityChange += OnComponentAuthorityChange; + } + } + + /// + public void Dispose() + { + if (component != null) + { + component.OnAuthorityChange -= OnComponentAuthorityChange; + } + + for (var i = 0; i < dispatcherCallbackKeys.Count; i++) + { + communicator.Remove(dispatcherCallbackKeys[i]); + } + } + + private void OnComponentAuthorityChange(Authority authority) + { + if (component.Authority != Authority.Authoritative && component.Authority != Authority.AuthorityLossImminent) + { + requestIdToCallback.Clear(); + } + } + + private Option SendGenericCommand(CommandCallback callback, + Func sendRequestWithTimeoutMs, TimeSpan? timeout = null) + { + var callbackWrapper = new CommandCallbackWrapper(callback); + if (component != null && component.Authority != Authority.Authoritative && component.Authority != Authority.AuthorityLossImminent) + { + // This needs to be deferred, so that all callbacks are registered + // before they are actually called. + communicator.Defer(() => callbackWrapper.TriggerWithError(StatusCode.AuthorityLost, string.Format( + "Tried to send a command from (entity ID: {0}, component: {1}) without " + + "authority on that pair.", + component.EntityId, + component.GetType() + ))); + + return new Option(); + } + + var timeoutMs = timeout.HasValue ? (uint) timeout.Value.Milliseconds : DefaultCommandTimeoutMs; + var requestId = sendRequestWithTimeoutMs(timeoutMs); + requestIdToCallback.Add(requestId, callbackWrapper); + return requestId; + } + + /// + public void ReserveEntityId(CommandCallback callback, TimeSpan? timeout = null) + { + ReserveEntityIdInternal(callback, timeout); + } + + /// + public void ReserveEntityIds(CommandCallback callback, uint numberOfEntityIds, TimeSpan? timeout = null) + { + ReserveEntityIdsInternal(callback, numberOfEntityIds, timeout); + } + + public Option ReserveEntityIdInternal(CommandCallback callback, TimeSpan? timeout = null) + { + Func sendRequestWithTimeoutMs = + timeoutMs => communicator.SendReserveEntityIdRequest(timeoutMs).Id; + return SendGenericCommand(callback, sendRequestWithTimeoutMs, timeout); + } + + public Option ReserveEntityIdsInternal(CommandCallback callback, uint numberOfEntityIds, TimeSpan? timeout = null) + { + Func sendRequestWithTimeoutMs = + timeoutMs => communicator.SendReserveEntityIdsRequest(numberOfEntityIds, timeoutMs).Id; + return SendGenericCommand(callback, sendRequestWithTimeoutMs, timeout); + } + + /// + public void CreateEntity(EntityId reservedEntityId, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null) + { + CreateEntityInternal(reservedEntityId, template, callback, timeout); + } + + internal Option CreateEntityInternal(EntityId reservedEntityId, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null) + { + Func sendRequestWithTimeoutMs = + timeoutMs => communicator.SendCreateEntityRequest(template, reservedEntityId, timeoutMs).Id; + return SendGenericCommand(callback, sendRequestWithTimeoutMs, timeout); + } + + /// + public void CreateEntity(Worker.Entity template, CommandCallback callback, TimeSpan? timeout = null) + { + CreateEntityInternal(template, callback, timeout); + } + + internal Option CreateEntityInternal(Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null) + { + Func sendRequestWithTimeoutMs = + timeoutMs => communicator.SendCreateEntityRequest(template, null, timeoutMs).Id; + return SendGenericCommand(callback, sendRequestWithTimeoutMs, timeout); + } + + /// + public void DeleteEntity(EntityId entityId, CommandCallback callback, TimeSpan? timeout = null) + { + DeleteEntityInternal(entityId, callback, timeout); + } + + internal Option DeleteEntityInternal(EntityId entityId, CommandCallback callback, TimeSpan? timeout = null) + { + Func sendRequestWithTimeoutMs = + timeoutMs => communicator.SendDeleteEntityRequest(entityId, timeoutMs).Id; + return SendGenericCommand(callback, sendRequestWithTimeoutMs, timeout); + } + + /// + public void SendQuery(EntityQuery query, CommandCallback callback, TimeSpan? timeout = null) + { + SendQueryInternal(query, callback, timeout); + } + + internal Option SendQueryInternal(EntityQuery query, CommandCallback callback, TimeSpan? timeout = null) + { + Func sendRequestWithTimeoutMs = + timeoutMs => communicator.SendEntityQueryRequest(query, timeoutMs).Id; + return SendGenericCommand(callback, sendRequestWithTimeoutMs, timeout); + } + + /// + public void SendCommand(EntityId entityId, ICommandRequest rawRequest, Func, TResponse> extractResponseFunc, + CommandCallback callback, TimeSpan? timeout = null, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) where TCommand : ICommandMetaclass, new() + { + SendCommandInternal(entityId, rawRequest, extractResponseFunc, callback, timeout, commandDelivery); + } + + public Option SendCommandInternal(EntityId entityId, ICommandRequest rawRequest, + Func, TResponse> extractResponseFunc, CommandCallback callback, + TimeSpan? timeout, CommandDelivery commandDelivery) where TCommand : ICommandMetaclass, new() + { + if (!commandResponseThunksRegistered.Contains(typeof(TCommand))) + { + var callbackKey = RegisterCommandResponse(extractResponseFunc); + dispatcherCallbackKeys.Add(callbackKey); + commandResponseThunksRegistered.Add(typeof(TCommand)); + } + + Func sendRequestWithTimeoutMs = + timeoutMs => SendCommandRequest(entityId, rawRequest, timeoutMs, commandDelivery); + return SendGenericCommand(callback, sendRequestWithTimeoutMs, timeout); + } + + /// + /// This method is required to prevent Unity compiler issues. + /// + private ulong RegisterCommandResponse(Func, TResponse> extractResponseFunc) + where TCommand : ICommandMetaclass, new() + { + return communicator.RegisterCommandResponse(CreateOnResponseThunk, TResponse>( + op => op.RequestId.Id, op => op.StatusCode, op => op.Message, + op => ExtractResponse(extractResponseFunc, op))); + } + + /// + /// This method is required to prevent Unity compiler issues. + /// + private Option ExtractResponse(Func, TResponse> extractResponseFunc, + CommandResponseOp op) where TCommand : ICommandMetaclass, new() + { + return op.Response.HasValue ? extractResponseFunc(op.Response.Value) : new Option(); + } + + /// + /// This method is required to prevent Unity compiler issues. + /// + private uint SendCommandRequest(EntityId entityId, ICommandRequest rawRequest, uint timeoutMs, CommandDelivery commandDelivery) + where TCommand : ICommandMetaclass, new() + { + return communicator.SendCommandRequest(entityId, rawRequest, timeoutMs, commandDelivery).Id; + } + + private Action CreateOnResponseThunk(Func requestIdFromOp, + Func statusCodeFromOp, Func errorMessageFromOp, Func> userResponseFromOp) + { + return op => + { + ICommandCallbackWrapper callbackWrapper; + var requestId = requestIdFromOp(op); + if (!requestIdToCallback.TryGetValue(requestId, out callbackWrapper)) + { + return; + } + + requestIdToCallback.Remove(requestId); + CommandCallbackWrapper typedCallbackWrapper; + try + { + typedCallbackWrapper = (CommandCallbackWrapper) callbackWrapper; + } + catch (InvalidCastException exception) + { + // ERROR: could not cast callback to expected type + Debug.LogException(exception); + return; + } + + typedCallbackWrapper.TriggerWithStatus(statusCodeFromOp(op), userResponseFromOp(op), + errorMessageFromOp(op)); + }; + } + + /// + /// Removes requestId from the dictionary of known request ids. + /// + /// + /// This is used by the old Commander implementation, and should be removed when possible. + /// + public void ForgetRequestId(uint requestId) + { + requestIdToCallback.Remove(requestId); + } + + public ICommandResponseHandler ReserveEntityId(TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => ReserveEntityId(callback, timeout)); + } + + public ICommandResponseHandler ReserveEntityIds(uint numberOfEntityIds, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => ReserveEntityIds(callback, numberOfEntityIds, timeout)); + } + + public ICommandResponseHandler CreateEntity(EntityId reservedEntityId, Worker.Entity template, + TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => CreateEntity(reservedEntityId, template, callback, timeout)); + } + + public ICommandResponseHandler CreateEntity(Worker.Entity template, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => CreateEntity(template, callback, timeout)); + } + + public ICommandResponseHandler DeleteEntity(EntityId entityId, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => DeleteEntity(entityId, callback, timeout)); + } + + public ICommandResponseHandler SendQuery(EntityQuery query, TimeSpan? timeout = null) + { + return CommandResponseHandler.Wrap(callback => SendQuery(query, callback, timeout)); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ComponentCommander.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ComponentCommander.cs.meta new file mode 100644 index 0000000..33c349b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ComponentCommander.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 689276c4040612c4484c6548a34c4252 +timeCreated: 1483529603 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandCallbackWrapper.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandCallbackWrapper.cs new file mode 100644 index 0000000..fc1b92c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandCallbackWrapper.cs @@ -0,0 +1,128 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Collections; +using Improbable.Worker; + +namespace Improbable.Unity.Core +{ + interface ICommandCallbackWrapper + { + void TriggerWithError(StatusCode statusCode, string errorMessage); + void TriggerWithAuthorityError(); + } + + class CommandCallbackWrapper : ICommandCallbackWrapper + { + private readonly CommandCallback commandCallback; + + public CommandCallbackWrapper(CommandCallback commandCallback) + { + this.commandCallback = commandCallback; + } + + public void TriggerWithStatus(StatusCode statusCode, Option userResponse, string errorMessage = null) + { + if (commandCallback == null) + { + // Fire and forget succeeded + return; + } + + commandCallback(new CommandCallbackResponse + { + StatusCode = statusCode, + ErrorMessage = errorMessage, + Response = userResponse + }); + } + + public void TriggerWithError(StatusCode statusCode, string errorMessage) + { + if (commandCallback == null) + { + // Fire and forget failed + return; + } + + commandCallback(new CommandCallbackResponse + { + StatusCode = statusCode, + ErrorMessage = errorMessage, + Response = new Option() + }); + } + + public void TriggerWithAuthorityError() + { + TriggerWithError(StatusCode.AuthorityLost, "This worker does not have write authority on the given entity/component pair."); + } + } + + /// + /// A callback of this type must be passed with each command request, in order to return the response to the caller. + /// The callback is passed an ICommandCallbackResponse object when it is invoked. + /// + /// The response. + public delegate void CommandCallback(ICommandCallbackResponse response); + + /// + /// Callback invoked when a command is successfully executed. + /// + /// + /// + public delegate void CommandSuccessCallback(TResponse response); + + /// + /// Callback invoked when a command invocation fails. + /// + /// + public delegate void CommandFailureCallback(ICommandErrorDetails response); + + /// + /// This wraps up the response from a command call. + /// + public interface ICommandCallbackResponse : ICommandErrorDetails + { + /// + /// The value returned by the callee. Might be null in case no response was received. + /// + Option Response { get; } + } + + /// + /// Contains the details of the command invocation error. + /// + public interface ICommandErrorDetails + { + /// + /// Whether or not the command was successful. + /// + StatusCode StatusCode { get; } + + /// + /// The error message returned in case the command was not successful. + /// + string ErrorMessage { get; } + } + + /// + /// This wraps up the response from a command call. + /// + public struct CommandCallbackResponse : ICommandCallbackResponse + { + /// + /// Whether or not the command was successful. + /// + public StatusCode StatusCode { get; set; } + + /// + /// The error message returned in case the command was not successful. + /// + public string ErrorMessage { get; set; } + + /// + /// The value returned by the callee. Might be null in case no response was received. + /// + public Option Response { get; set; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandCallbackWrapper.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandCallbackWrapper.cs.meta new file mode 100644 index 0000000..8db886c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandCallbackWrapper.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1fc2184b99b3b1446b6d1c319c8ab9a1 +timeCreated: 1483529603 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandResponseHandler.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandResponseHandler.cs new file mode 100644 index 0000000..8d689fc --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandResponseHandler.cs @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// Allows for registration of callbacks for success or failure of commands. + /// + public interface ICommandResponseHandler + { + /// + /// Registers callback to be invoked when command succeeded. + /// + ICommandResponseHandler OnSuccess(CommandSuccessCallback successCallback); + + /// + /// Registers callback to be invoked when command failed. + /// + ICommandResponseHandler OnFailure(CommandFailureCallback failureCallback); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandResponseHandler.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandResponseHandler.cs.meta new file mode 100644 index 0000000..e315285 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandResponseHandler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 94c44d95aa2c98c47b868b6418a0881e +timeCreated: 1485452592 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandSender.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandSender.cs new file mode 100644 index 0000000..6fb8187 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandSender.cs @@ -0,0 +1,110 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Entity.Component; +using Improbable.Unity.Core.EntityQueries; +using Improbable.Worker; +using Improbable.Worker.Query; + +namespace Improbable.Unity.Core +{ + /// + /// An interface to the old style of sending commands from component writers. + /// + public interface ICommandSender + { + /// + /// Invokes a command on an entity's component. + /// The callback may be null if you wish to 'fire and forget'. + /// + void SendCommand(IComponentWriter writer, + ICommandDescriptor commandDescriptor, TRequest request, + EntityId entityId, CommandCallback callback, TimeSpan? timeout = null, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) + where TCommand : ICommandMetaclass, new(); + + /// + /// Invokes a command on an entity's component. + /// Returns an object that allows you to specify + /// callbacks to be invoked in case of the command's success + /// or failure. + /// + ICommandResponseHandler SendCommand(IComponentWriter writer, + ICommandDescriptor commandDescriptor, + TRequest request, EntityId entityId, TimeSpan? timeout = null, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) + where TCommand : ICommandMetaclass, new(); + + /// + /// Reserves an entity ID for later use in creating an entity. + /// + void ReserveEntityId(IComponentWriter writer, CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Reserves a number of entity IDs for later use in creating a number of entities. + /// + void ReserveEntityIds(IComponentWriter writer, CommandCallback callback, uint numberOfEntityIds, TimeSpan? timeout = null); + + /// + /// Reserves an entity ID for later use in creating an entity. + /// + // #SNIPPET_START reserve_entity_complex_interface + ICommandResponseHandler ReserveEntityId(IComponentWriter writer, TimeSpan? timeout = null); + // #SNIPPET_END reserve_entity_complex_interface + + /// + /// Reserves a number of entity IDs for later use in creating a number of entities. + /// + ICommandResponseHandler ReserveEntityIds(IComponentWriter writer, uint numberOfEntityIds, TimeSpan? timeout = null); + + /// + /// Creates an entity with a previously reserved entity id. + /// + void CreateEntity(IComponentWriter writer, EntityId reservedEntityId, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Creates an entity with a previously reserved entity id. + /// + // #SNIPPET_START create_entity_complex_interface + ICommandResponseHandler CreateEntity(IComponentWriter writer, EntityId reservedEntityId, + Worker.Entity template, TimeSpan? timeout = null); + // #SNIPPET_END create_entity_complex_interface + + /// + /// Creates an entity without needing to manually reserve an entity id. + /// + void CreateEntity(IComponentWriter writer, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Creates an entity without needing to manually reserve an entity id. + /// + // #SNIPPET_START create_entity_simple_interface + ICommandResponseHandler CreateEntity(IComponentWriter writer, + Worker.Entity template, TimeSpan? timeout = null); + // #SNIPPET_END create_entity_simple_interface + + /// + /// Deletes an entity. + /// + void DeleteEntity(IComponentWriter writer, EntityId entityId, + CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Deletes an entity. + /// + // #SNIPPET_START delete_entity_interface + ICommandResponseHandler DeleteEntity(IComponentWriter writer, EntityId entityId, TimeSpan? timeout = null); + // #SNIPPET_END delete_entity_interface + + /// + /// Sends a query and gets back a response. + /// + void SendQuery(IComponentWriter writer, EntityQuery query, + CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Sends a query and gets back a response. + /// + ICommandResponseHandler SendQuery(IComponentWriter writer, EntityQuery query, TimeSpan? timeout = null); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandSender.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandSender.cs.meta new file mode 100644 index 0000000..04b2cb7 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/ICommandSender.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bcc6dffa1a78f4a448551df65bf9362c +timeCreated: 1486074870 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IComponentCommander.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IComponentCommander.cs new file mode 100644 index 0000000..8240f10 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IComponentCommander.cs @@ -0,0 +1,18 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Worker; + +namespace Improbable.Unity.Core +{ + public interface IComponentCommander : IWorldCommander, IDisposable + { + /// + /// Sends a command. This method should not need to be used directly, as extension methods on ICommander are generated + /// for each command defined in the schema. + /// + void SendCommand(EntityId entityId, ICommandRequest rawRequest, + Func, TResponse> extractResponseFunc, CommandCallback callback, TimeSpan? timeout = null, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) + where TCommand : ICommandMetaclass, new(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IComponentCommander.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IComponentCommander.cs.meta new file mode 100644 index 0000000..93a16c2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IComponentCommander.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5543840c1cf65014f88ea51c82814f01 +timeCreated: 1486392899 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IDescriptorCommander.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IDescriptorCommander.cs new file mode 100644 index 0000000..b777381 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IDescriptorCommander.cs @@ -0,0 +1,32 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Entity.Component; +using Improbable.Worker; + +namespace Improbable.Unity.Core +{ + /// + /// An interface to the commander that uses command descriptors to send commands. + /// + public interface IDescriptorCommander + { + /// + /// Invokes a command on an entity's component. + /// The callback may be null if you wish to 'fire and forget'. + /// + void SendCommand(ICommandDescriptor commandDescriptor, + TRequest request, EntityId entityId, CommandCallback callback, TimeSpan? timeout = null, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) + where TCommand : ICommandMetaclass, new(); + + /// + /// Invokes a command on an entity's component. + /// Returns an object that allows you to specify + /// callbacks to be invoked in case of the command's success + /// or failure. + /// + ICommandResponseHandler SendCommand(ICommandDescriptor commandDescriptor, + TRequest request, EntityId entityId, TimeSpan? timeout = null, CommandDelivery commandDelivery = CommandDelivery.RoundTrip) + where TCommand : ICommandMetaclass, new(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IDescriptorCommander.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IDescriptorCommander.cs.meta new file mode 100644 index 0000000..0c95a11 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IDescriptorCommander.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b34635a36a9251e4295b693b691b1c75 +timeCreated: 1489483763 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorkerCommandSender.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorkerCommandSender.cs new file mode 100644 index 0000000..6caa275 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorkerCommandSender.cs @@ -0,0 +1,9 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// An interface to the old style of sending commands from workers. + /// + public interface IWorkerCommandSender : IDescriptorCommander, IWorldCommander { } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorkerCommandSender.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorkerCommandSender.cs.meta new file mode 100644 index 0000000..fb44d12 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorkerCommandSender.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9256b3fb117711d46b75410117effb4d +timeCreated: 1486074870 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorldCommander.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorldCommander.cs new file mode 100644 index 0000000..1dcac54 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorldCommander.cs @@ -0,0 +1,100 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Unity.Core.EntityQueries; +using Improbable.Worker.Query; + +namespace Improbable.Unity.Core +{ +#pragma warning disable 1584 + /// + /// An interface to the commander that sends world commands. + /// + public interface IWorldCommander + { + /// + /// Reserves an entity ID for later use in creating an entity. + /// + void ReserveEntityId(CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Reserves a number of entity IDs for later use in creating a number of entities. + /// + void ReserveEntityIds(CommandCallback callback, uint numberOfEntityIds, TimeSpan? timeout = null); + + /// + /// Reserves an entity ID for later use in creating an entity. + /// Returns an object that allows you to specify + /// callbacks to be invoked in case of the command's success + /// or failure. + /// + ICommandResponseHandler ReserveEntityId(TimeSpan? timeout = null); + + /// + /// Reserves a number of entity IDs for later use in creating a number of entities. + /// Returns an object that allows you to specify + /// callbacks to be invoked in case of the command's success + /// or failure. + /// + ICommandResponseHandler ReserveEntityIds(uint numberOfEntityIds, TimeSpan? timeout = null); + + /// + /// Creates an entity with a previously reserved entity id. + /// + void CreateEntity(EntityId reservedEntityId, Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Creates an entity with a previously reserved entity id. + /// Returns an object that allows you to specify + /// callbacks to be invoked in case of the command's success + /// or failure. + /// + ICommandResponseHandler CreateEntity(EntityId reservedEntityId, Worker.Entity template, TimeSpan? timeout = null); + + /// + /// Creates an entity without needing to manually reserve an entity id. + /// May take up to 2 * timeout to complete. + /// + void CreateEntity(Worker.Entity template, + CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Creates an entity without needing to manually reserve an entity id. + /// May take up to 2 * timeout to complete. + /// Returns an object that allows you to specify + /// callbacks to be invoked in case of the command's success + /// or failure. + /// + ICommandResponseHandler CreateEntity(Worker.Entity template, TimeSpan? timeout = null); + + /// + /// Deletes an entity. + /// + void DeleteEntity(EntityId entityId, + CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Deletes an entity. + /// Returns an object that allows you to specify + /// callbacks to be invoked in case of the command's success + /// or failure. + /// + ICommandResponseHandler DeleteEntity(EntityId entityId, TimeSpan? timeout = null); + + /// + /// Sends a query and gets back a response. + /// + void SendQuery(EntityQuery query, CommandCallback callback, TimeSpan? timeout = null); + + /// + /// Sends a query and gets back a response. + /// Returns an object that allows you to specify + /// callbacks to be invoked in case of the command's success + /// or failure. + /// + ICommandResponseHandler SendQuery(EntityQuery query, TimeSpan? timeout = null); + } + +#pragma warning disable 1584 +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorldCommander.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorldCommander.cs.meta new file mode 100644 index 0000000..3b23513 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/IWorldCommander.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0d0ccff2b146f3a46ab15c7a4f1d50ff +timeCreated: 1489483763 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/SpatialCommunicator.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/SpatialCommunicator.cs new file mode 100644 index 0000000..5ac9811 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/SpatialCommunicator.cs @@ -0,0 +1,231 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Collections; +using Improbable.Worker; +using Improbable.Worker.Query; + +namespace Improbable.Unity.Core +{ + /// + /// Implementation of + /// + class SpatialCommunicator : ISpatialCommunicator + { + private Connection connection; + private Dispatcher dispatcher; + private IDeferredActionDispatcher deferredActionDispatcher; + + /// + /// Creates a new Communicator with the given connection and dispatcher. + /// + public SpatialCommunicator(Connection connection, Dispatcher dispatcher, IDeferredActionDispatcher deferredActionDispatcher) + { + this.connection = connection; + this.dispatcher = dispatcher; + this.deferredActionDispatcher = deferredActionDispatcher; + } + + /// + public void SendComponentUpdate(EntityId entityId, IComponentUpdate update, bool legacyCallbackSemantics = false) where TComponent : IComponentMetaclass + { + connection.SendComponentUpdate(entityId, update, legacyCallbackSemantics); + } + + /// + public void SendCommandResponse(RequestId> requestId, ICommandResponse response) where TComponent : ICommandMetaclass, new() + { + connection.SendCommandResponse(requestId, response); + } + + public void SendAuthorityLossImminentAcknowledgement(EntityId entityId) where C : IComponentMetaclass + { + connection.SendAuthorityLossImminentAcknowledgement(entityId); + } + + /// + public RequestId> SendCommandRequest(EntityId entityId, ICommandRequest request, Option timeout, CommandDelivery commandDelivery) where TCommand : ICommandMetaclass, new() + { + return connection.SendCommandRequest(entityId, request, timeout, new CommandParameters { AllowShortCircuiting = commandDelivery == CommandDelivery.ShortCircuit }); + } + + /// + public RequestId SendReserveEntityIdRequest(Option timeout) + { +#pragma warning disable 618 + return connection.SendReserveEntityIdRequest(timeout); +#pragma warning restore 618 + } + + /// + public RequestId SendReserveEntityIdsRequest(uint numberOfEntityIds, Option timeout) + { + return connection.SendReserveEntityIdsRequest(numberOfEntityIds, timeout); + } + + /// + public RequestId SendCreateEntityRequest(Worker.Entity template, Option entityId, Option timeout) + { + return connection.SendCreateEntityRequest(template, entityId, timeout); + } + + /// + public RequestId SendDeleteEntityRequest(EntityId entityId, Option timeout) + { + return connection.SendDeleteEntityRequest(entityId, timeout); + } + + /// + public RequestId SendEntityQueryRequest(EntityQuery query, Option timeout) + { + return connection.SendEntityQueryRequest(query, timeout); + } + + /// + public void Defer(Action action) + { + deferredActionDispatcher.DeferAction(action); + } + + /// + public ulong RegisterAddComponent(Action> callback) where TComponent : IComponentMetaclass + { + return dispatcher.OnAddComponent(callback); + } + + /// + public ulong RegisterRemoveComponent(Action callback) where TComponent : IComponentMetaclass + { + return dispatcher.OnRemoveComponent(callback); + } + + /// + public ulong RegisterComponentUpdate(Action> callback) where TComponent : IComponentMetaclass + { + return dispatcher.OnComponentUpdate(callback); + } + + /// + public ulong RegisterCommandRequest(Action> callback) where TCommand : ICommandMetaclass, new() + { + return dispatcher.OnCommandRequest(callback); + } + + /// + public ulong RegisterAuthorityChange(Action callback) where TComponent : IComponentMetaclass + { + return dispatcher.OnAuthorityChange(callback); + } + + /// + public void RemoveDispatcherCallback(ulong callbackKey) + { + dispatcher.Remove(callbackKey); + } + + /// + public ulong RegisterCommandResponse(Action> response) where TCommand : ICommandMetaclass, new() + { + return dispatcher.OnCommandResponse(response); + } + + /// + public ulong RegisterReserveEntityIdResponse(Action callback) + { +#pragma warning disable 618 + return dispatcher.OnReserveEntityIdResponse(callback); +#pragma warning restore 618 + } + + /// + public ulong RegisterReserveEntityIdsResponse(Action callback) + { + return dispatcher.OnReserveEntityIdsResponse(callback); + } + + /// + public ulong RegisterCreateEntityResponse(Action callback) + { + return dispatcher.OnCreateEntityResponse(callback); + } + + /// + public ulong RegisterDeleteEntityResponse(Action callback) + { + return dispatcher.OnDeleteEntityResponse(callback); + } + + /// + public ulong RegisterEntityQueryResponse(Action callback) + { + return dispatcher.OnEntityQueryResponse(callback); + } + + /// + public void Remove(ulong callbackKey) + { + dispatcher.Remove(callbackKey); + } + + /// + public void RegisterComponentInterest(EntityId entityId, System.Collections.Generic.Dictionary interestOverrides) + { + connection.SendComponentInterest(entityId, interestOverrides); + } + + /// + public Authority GetAuthority(EntityId writerEntityId, uint writerComponentId) + { + Map authorityForComponentsOfEntity; + + if (!SpatialOS.Dispatcher.Authority.TryGetValue(writerEntityId, out authorityForComponentsOfEntity)) + { + throw new InvalidOperationException( + string.Format("Authority information for writer entity {0} could not be accessed.", writerEntityId)); + } + + Authority authorityForComponent; + + if (!authorityForComponentsOfEntity.TryGetValue(writerComponentId, out authorityForComponent)) + { + // It is possible that this worker cannot even see the component. + // That means it certainly is not authoritative over it. + authorityForComponent = Authority.NotAuthoritative; + } + + return authorityForComponent; + } + + public void TriggerComponentAuthorityChanged(EntityId entityId, IComponentMetaclass componentId, + Authority authority, object component) + { + if (ComponentAuthorityChanged != null) + { + ComponentAuthorityChanged(entityId, componentId, authority, component); + } + } + + /// + public event OnAuthorityChangedCallback ComponentAuthorityChanged; + + public void AttachConnection(Connection connectionToUse) + { + this.connection = connectionToUse; + } + + public ulong RegisterCriticalSection(Action criticalSection) + { + return dispatcher.OnCriticalSection(criticalSection); + } + + public ulong RegisterAddEntity(Action addEntity) + { + return dispatcher.OnAddEntity(addEntity); + } + + public ulong RegisterRemoveEntity(Action removeEntity) + { + return dispatcher.OnRemoveEntity(removeEntity); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/SpatialCommunicator.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/SpatialCommunicator.cs.meta new file mode 100644 index 0000000..978ff5f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Commands/SpatialCommunicator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7739a80e57e656444b381b740f858445 +timeCreated: 1483529603 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration.meta new file mode 100644 index 0000000..682c5dc --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 2c9cd445986c5f74e8f11fe8fdf82f1d +folderAsset: yes +timeCreated: 1469522723 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/CommandLineConfigNames.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/CommandLineConfigNames.cs new file mode 100644 index 0000000..187fbb6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/CommandLineConfigNames.cs @@ -0,0 +1,22 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Configuration +{ + /// + /// Configuration options that are only available via the command line. + /// + public static class CommandLineConfigNames + { + // AppName is deprecated. Not using the obsolete tag here to avoid spamming warning messages. + public const string AppName = "appName"; + public const string ProjectName = "projectName"; + public const string AssetDatabaseStrategy = "assetDatabaseStrategy"; + public const string AssetLoadingRetryBackoffMilliseconds = "asstLoadingRetryBackoffMilliseconds"; + public const string LocalAssetDatabasePath = "localAssetDatabasePath"; + public const string LoginToken = "loginToken"; + public const string MaxAssetLoadingRetries = "maxAssetLoadingRetries"; + public const string RefreshToken = "refreshToken"; + public const string UseLocalPrefabs = "useLocalPrefabs"; + public const string UseExternalIpForBridge = "useExternalIpForBridge"; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/CommandLineConfigNames.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/CommandLineConfigNames.cs.meta new file mode 100644 index 0000000..3585d56 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/CommandLineConfigNames.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1740dd0c285ab6f41a626b631f8620c1 +timeCreated: 1469173176 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/Defaults.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/Defaults.cs new file mode 100644 index 0000000..71b535a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/Defaults.cs @@ -0,0 +1,43 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Worker; + +namespace Improbable.Unity.Configuration +{ + public static class Defaults + { + public const string DeploymentTag = "prod"; + + [Obsolete("Please use Improbable.Unity.Core.CountBasedSpawnLimiter and SpatialOS.EntitySpawnLimiter.")] + public const int EntityCreationLimitPerFrame = 100; + + public const NetworkConnectionType LinkProtocol = NetworkConnectionType.RakNet; + public const bool LogDebugToSpatialOs = false; + public const bool LogAssertToSpatialOs = false; + public const bool LogWarningToSpatialOs = true; + public const bool LogErrorToSpatialOs = true; + public const bool LogExceptionToSpatialOs = true; + + public const string InfraServiceUrl = "https://api.spatial.improbable.io"; + public const string LocatorHost = "locator.improbable.io"; + public const string ProtocolLogPrefix = "protocol-"; + public const uint ProtocolLogMaxFileBytes = 100U * 1024U * 1024U; + public const bool ProtocolLoggingOnStartup = false; + public const uint RaknetHeartbeatTimeoutMillis = Worker.Defaults.RakNetHeartbeatTimeoutMillis; + public const uint ReceiveQueueCapacity = 32768U; + public const string ReceptionistHost = "127.0.0.1"; + public const ushort ReceptionistPort = 7777; + public const uint SendQueueCapacity = 16384U; + + [Obsolete("As of 12.1, this no longer does anything, and will be removed in a future release.")] + public const bool ShowDebugTraces = false; + + public const byte TcpMultiplexLevel = Worker.Defaults.TcpMultiplexLevel; + public const bool UseExternalIp = false; + public const bool UseInstrumentation = true; + + [Obsolete("As of 12.1, this no longer does anything, and will be removed in a future release.")] + public const bool UsePerInstanceAssetCache = true; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/Defaults.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/Defaults.cs.meta new file mode 100644 index 0000000..89413f4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/Defaults.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 733c8fcc926571644845cde0db6dd3a4 +timeCreated: 1469532384 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/EditableConfigNames.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/EditableConfigNames.cs new file mode 100644 index 0000000..e5c789c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/EditableConfigNames.cs @@ -0,0 +1,56 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; + +namespace Improbable.Unity.Configuration +{ + /// + /// Configuration options that are available for the users to edit from within Unity. + /// + public static class EditableConfigNames + { + public const string AssemblyName = "assemblyName"; + public const string DeploymentId = "deploymentId"; + public const string DeploymentTag = "deploymentTag"; + + [Obsolete("Please use WorkerId instead.")] + public const string EngineId = "engineId"; + + [Obsolete("Please use WorkerType instead.")] + public const string EngineType = "engineType"; + + [Obsolete("Please use Improbable.Unity.Core.CountBasedSpawnLimiter and SpatialOS.EntitySpawnLimiter.")] + public const string EntityCreationLimitPerFrame = "entityCreationLimitPerFrame"; + + public const string InfraServiceUrl = "infraServicesUrl"; + public const string LinkProtocol = "linkProtocol"; + public const string LocatorHost = "locatorHost"; + public const string LogDebugToSpatialOs = "logDebugToSpatialOs"; + public const string LogAssertToSpatialOs = "logAssertToSpatialOs"; + public const string LogWarningToSpatialOs = "logWarningToSpatialOs"; + public const string LogErrorToSpatialOs = "logErrorToSpatialOs"; + public const string LogExceptionToSpatialOs = "logExceptionToSpatialOs"; + public const string LoginToken = "loginToken"; + public const string TcpMultiplexLevel = "tcpMultiplexLevel"; + public const string ProtocolLogPrefix = "protocolLogPrefix"; + public const string ProtocolLoggingOnStartup = "protocolLoggingOnStartup"; + public const string ProtocolLogMaxFileBytes = "protocolLogMaxFileBytes"; + public const string RaknetHeartbeatTimeoutMillis = "raknetHeartbeatTimeoutMs"; + public const string ReceptionistHost = "receptionistHost"; + public const string ReceptionistPort = "receptionistPort"; + + [Obsolete("As of 12.1, this no longer does anything, and will be removed in a future release.")] + public const string ShowDebugTraces = "showDebugTraces"; + + public const string SteamToken = "steamToken"; + public const string UsePrefabPooling = "usePrefabPooling"; + public const string UseInstrumentation = "useInstrumentation"; + + [Obsolete("As of 12.1, this no longer does anything, and will be removed in a future release.")] + public const string UsePerInstanceAssetCache = "usePerInstanceAssetCache"; + + public const string WorkerId = "workerId"; + public const string WorkerType = "workerType"; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/EditableConfigNames.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/EditableConfigNames.cs.meta new file mode 100644 index 0000000..2083c75 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/EditableConfigNames.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ad5e13cd9bfc340488a3552b6354aac6 +timeCreated: 1444833361 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfiguration.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfiguration.cs new file mode 100644 index 0000000..0583ed3 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfiguration.cs @@ -0,0 +1,338 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Improbable.Unity.Core; +using Improbable.Unity.Util; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Configuration +{ + /// + /// Provides easy access to SpatialOS-related configuration properties. + /// Arguments provided on the command line override any settings provided by code. + /// + public class WorkerConfiguration + { + private const string ObsolescenceWarningFormat = "Command line parameter '{0}' is obsolete please use '{1}' instead."; + + private readonly Dictionary commandLineDictionary; + + private string appName; + + [Obsolete("WorkerConfiguration.AppName is deprecated. Please use WorkerConfiguration.ProjectName instead.")] + public string AppName + { + get { return appName; } + private set { appName = value; } + } + + private string projectName; + + public string ProjectName + { + get { return !string.IsNullOrEmpty(projectName) ? projectName : appName; } + private set { projectName = value; } + } + + public string AssemblyName { get; private set; } + + public string DeploymentId { get; private set; } + + public string DeploymentTag { get; private set; } + + [Obsolete("Please use WorkerId instead.")] + public string EngineId + { + get { return WorkerId; } + private set { WorkerId = value; } + } + + [Obsolete("Please use WorkerPlatform instead.")] + public WorkerPlatform EnginePlatform + { + get { return WorkerPlatform; } + private set { WorkerPlatform = value; } + } + + public string WorkerId { get; private set; } + + public WorkerPlatform WorkerPlatform { get; private set; } + + /// + /// Limits the number of new entities that will be added each frame. + /// Set this to 0 to create entities as soon as they are received. + /// + [Obsolete("Please use Improbable.Unity.Core.CountBasedSpawnLimiter and SpatialOS.EntitySpawnLimiter.")] + public int EntityCreationLimitPerFrame { get; private set; } + + public string InfraServiceUrl { get; private set; } + + public string LocatorHost { get; private set; } + + public string ReceptionistHost { get; private set; } + + public ushort ReceptionistPort { get; private set; } + + public NetworkConnectionType LinkProtocol { get; private set; } + + public string LoginToken { get; private set; } + + public bool ProtocolLoggingOnStartup { get; private set; } + + public string ProtocolLogPrefix { get; private set; } + + public uint ProtocolLogMaxFileBytes { get; private set; } + + public uint RaknetHeartbeatTimeoutMillis { get; private set; } + + public uint ReceiveQueueCapacity { get; private set; } + + public uint SendQueueCapacity { get; private set; } + + [Obsolete("As of 12.1, this no longer does anything, and will be removed in a future release.")] + public bool ShowDebugTraces { get; private set; } + + public string SteamToken { get; private set; } + + public byte TcpMultiplexLevel { get; private set; } + + public bool UseExternalIp { get; private set; } + + public bool UseInstrumentation { get; private set; } + + [Obsolete("As of 12.1, this no longer does anything, and will be removed in a future release.")] + public bool UsePerInstanceAssetCache { get; private set; } + + public bool UsePrefabPooling { get; private set; } + + public bool LogDebugToSpatialOs { get; private set; } + + public bool LogAssertToSpatialOs { get; private set; } + + public bool LogWarningToSpatialOs { get; private set; } + + public bool LogErrorToSpatialOs { get; private set; } + + public bool LogExceptionToSpatialOs { get; private set; } + + /// + /// Constructs a new instance of WorkerConfiguration. + /// + /// User-configured data used to configure the worker. + /// + /// A list of arguments specified on the command line. + /// If null, defaults to + /// global::System.Environment.GetCommandLineArgs() + /// + public WorkerConfiguration(WorkerConfigurationData data, IList commandLineArguments = null) + { + if (commandLineArguments == null) + { + commandLineArguments = global::System.Environment.GetCommandLineArgs(); + } + + Debug.LogFormat("Command line {0}", string.Join(" ", commandLineArguments.ToArray())); + + commandLineDictionary = CommandLineUtil.ParseCommandLineArgs(commandLineArguments); + + ProjectName = string.Empty; + + if (Application.isEditor) + { + // Read ProjectName from the spatialos.json file, if not already specified. + // This is only done in Editor mode, as we do not expect spatialos.json to exist outside of dev environment. + + if (!commandLineDictionary.ContainsKey(CommandLineConfigNames.ProjectName) && !commandLineDictionary.ContainsKey(CommandLineConfigNames.AppName)) + { + try + { + ProjectName = ProjectDescriptor.Load().Name; + } + catch (Exception e) + { + Debug.LogErrorFormat("Cannot read project name from '{0}'. You will not be able to connect to a deployment. Underlying error: {1}", ProjectDescriptor.ProjectDescriptorPath, e); + } + } + } + + var defaultWorkerPlatform = data.SpatialOsApplication.WorkerPlatform; + +#pragma warning disable 618 + var workerPlatformString = GetObsoleteAndCurrentCommandLineValue(EditableConfigNames.WorkerType, EditableConfigNames.EngineType, string.Empty); +#pragma warning restore 618 + if (!string.IsNullOrEmpty(workerPlatformString)) + { + defaultWorkerPlatform = WorkerTypeUtils.FromWorkerName(workerPlatformString); + } + + var workerName = WorkerTypeUtils.ToWorkerName(data.SpatialOsApplication.WorkerPlatform); + + appName = GetCommandLineValue(CommandLineConfigNames.AppName, string.Empty); + ProjectName = GetCommandLineValue(CommandLineConfigNames.ProjectName, ProjectName); + AssemblyName = GetCommandLineValue(EditableConfigNames.AssemblyName, data.SpatialOsApplication.AssemblyName); + DeploymentId = GetCommandLineValue(EditableConfigNames.DeploymentId, data.SpatialOsApplication.DeploymentId); + DeploymentTag = GetCommandLineValue(EditableConfigNames.DeploymentTag, data.SpatialOsApplication.DeploymentTag); +#pragma warning disable 618 + WorkerId = GetObsoleteAndCurrentCommandLineValue(EditableConfigNames.WorkerId, EditableConfigNames.EngineId, workerName + Guid.NewGuid()); +#pragma warning restore 618 + WorkerPlatform = defaultWorkerPlatform; +#pragma warning disable 618 + EntityCreationLimitPerFrame = GetCommandLineValue(EditableConfigNames.EntityCreationLimitPerFrame, data.Unity.EntityCreationLimitPerFrame); +#pragma warning restore 618 + InfraServiceUrl = GetCommandLineValue(EditableConfigNames.InfraServiceUrl, data.Debugging.InfraServiceUrl); + ReceptionistHost = GetCommandLineValue(EditableConfigNames.ReceptionistHost, data.Networking.ReceptionistHost); + ReceptionistPort = GetCommandLineValue(EditableConfigNames.ReceptionistPort, data.Networking.ReceptionistPort); + LinkProtocol = GetCommandLineValue(EditableConfigNames.LinkProtocol, data.Networking.LinkProtocol); + LocatorHost = GetCommandLineValue(EditableConfigNames.LocatorHost, data.Networking.LocatorHost); + LoginToken = GetLoginTokenConfig(data); + ProtocolLogPrefix = GetCommandLineValue(EditableConfigNames.ProtocolLogPrefix, data.Debugging.ProtocolLogPrefix); + ProtocolLoggingOnStartup = GetCommandLineValue(EditableConfigNames.ProtocolLoggingOnStartup, data.Debugging.ProtocolLoggingOnStartup); + ProtocolLogMaxFileBytes = GetCommandLineValue(EditableConfigNames.ProtocolLogMaxFileBytes, data.Debugging.ProtocolLogMaxFileBytes); + RaknetHeartbeatTimeoutMillis = GetCommandLineValue(EditableConfigNames.RaknetHeartbeatTimeoutMillis, data.Networking.RaknetHeartbeatTimeoutMillis); + ReceiveQueueCapacity = data.Networking.ReceiveQueueCapacity; + SendQueueCapacity = data.Networking.SendQueueCapacity; + SteamToken = GetCommandLineValue(EditableConfigNames.SteamToken, data.Networking.SteamToken); + TcpMultiplexLevel = GetCommandLineValue(EditableConfigNames.TcpMultiplexLevel, data.Networking.TcpMultiplexLevel); + UseExternalIp = GetCommandLineValue(CommandLineConfigNames.UseExternalIpForBridge, Defaults.UseExternalIp) == false; // DEV-1120: The flag is flipped for legacy reasons. + UseInstrumentation = GetCommandLineValue(EditableConfigNames.UseInstrumentation, data.Debugging.UseInstrumentation); + UsePrefabPooling = GetCommandLineValue(EditableConfigNames.UsePrefabPooling, data.Unity.UsePrefabPooling); + + LogDebugToSpatialOs = GetCommandLineValue(EditableConfigNames.LogDebugToSpatialOs, data.Debugging.LogDebugToSpatialOs); + LogAssertToSpatialOs = GetCommandLineValue(EditableConfigNames.LogAssertToSpatialOs, data.Debugging.LogAssertToSpatialOs); + LogWarningToSpatialOs = GetCommandLineValue(EditableConfigNames.LogWarningToSpatialOs, data.Debugging.LogWarningToSpatialOs); + LogErrorToSpatialOs = GetCommandLineValue(EditableConfigNames.LogErrorToSpatialOs, data.Debugging.LogErrorToSpatialOs); + LogExceptionToSpatialOs = GetCommandLineValue(EditableConfigNames.LogExceptionToSpatialOs, data.Debugging.LogExceptionToSpatialOs); + + if (string.IsNullOrEmpty(ProjectName)) + { + throw new ArgumentException(string.Format("The ProjectName must be set with {0}, or via command-line argument +{1}", + Path.GetFileName(ProjectDescriptor.ProjectDescriptorPath), + CommandLineConfigNames.ProjectName)); + } + + if (Application.isEditor == false) + { + PrintWorkerConfigurationSettings(); + } + } + + private void PrintWorkerConfigurationSettings() + { + try + { + var properties = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); + + var sb = new StringBuilder(); + sb.AppendLine("WorkerConfiguration settings"); + foreach (var propertyInfo in properties) + { + var t = propertyInfo.PropertyType; + if (IsDictionary(t)) + { + PrintDictionaryValues(t, propertyInfo, sb); + } + else + { + sb.AppendFormat("{0} = {1}", propertyInfo.Name, propertyInfo.GetValue(this, null)); + sb.AppendLine(); + } + } + + Debug.Log(sb); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + + private bool IsDictionary(Type type) + { + return type.IsGenericType && + (typeof(Dictionary<,>).IsAssignableFrom(type.GetGenericTypeDefinition()) || + typeof(IDictionary<,>).IsAssignableFrom(type.GetGenericTypeDefinition())); + } + + private void PrintDictionaryValues(Type type, PropertyInfo propertyInfo, StringBuilder sb) + { + var dict = (IDictionary) propertyInfo.GetValue(this, null); + var values = string.Join(", ", dict.Keys.OfType().Select(kv => string.Format("{{{0} : {1}}}", kv, dict[kv])).ToArray()); + sb.AppendFormat("{0} = {{ {1} }}\n", propertyInfo.Name, values); + } + + /// + /// Gets a value specified on the command line, in the form "+key" "value" + /// + /// The type of the value. + /// The name of the key, without the leading +, e.g. "key" + /// The value to return if the key was not specified on the command line. + /// The value of the key, or defaultValue if the key was not specified on the command line. + public T GetCommandLineValue(string configKey, T defaultValue) + { + T configValue; + if (CommandLineUtil.TryGetConfigValue(commandLineDictionary, configKey, out configValue)) + { + return configValue; + } + + return defaultValue; + } + + /// + [Obsolete("Use CommandLineUtil.GetCommandLineValue")] + public static T GetCommandLineValue(IList arguments, string configKey, T defaultValue) + { + return CommandLineUtil.GetCommandLineValue(arguments, configKey, defaultValue); + } + + /// + /// Gets a value specified on the command line, in the form "+key" "value" + /// + /// The type of the value. + /// The name of the key, without the leading +, e.g. "key" + /// The value of the key + /// Thrown when the value of the given configuration is not found. + public T GetCommandLineValue(string configKey) + { + T configValue; + if (CommandLineUtil.TryGetConfigValue(commandLineDictionary, configKey, out configValue)) + { + return configValue; + } + + throw new Exception(string.Format("Could not find the configuration value for '{0}'.", configKey)); + } + + /// + /// Gets a value specified on the command line, falling back to the obsolete config key if not found. + /// Prints a warning about obsolescence. + /// + /// The name of the key, without the leading +, e.g. "key" + /// The name of the obsolete key, without the leading +, e.g. "key" + /// The value to return if the key was not specified on the command line. + /// The value of the key + private T GetObsoleteAndCurrentCommandLineValue(string currentConfigKey, string obsoleteConfigKey, T defaultValue) + { + var valueFromObsolete = GetCommandLineValue(obsoleteConfigKey, defaultValue); + if (!Equals(valueFromObsolete, defaultValue)) + { + Debug.LogWarningFormat(ObsolescenceWarningFormat, obsoleteConfigKey, currentConfigKey); + } + + return GetCommandLineValue(currentConfigKey, valueFromObsolete); + } + + private string GetLoginTokenConfig(WorkerConfigurationData data) + { + return GetCommandLineValue(EditableConfigNames.LoginToken, data.SpatialOsApplication.LoginToken); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfiguration.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfiguration.cs.meta new file mode 100644 index 0000000..18a2408 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfiguration.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d53cf74672fe53549a85938620778b26 +timeCreated: 1444833362 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfigurationData.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfigurationData.cs new file mode 100644 index 0000000..f530321 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfigurationData.cs @@ -0,0 +1,127 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Configuration +{ + /// + /// Settings to customise the worker. + /// + [Serializable] + public class WorkerConfigurationData + { + public Debugging Debugging = new Debugging(); + + public Networking Networking = new Networking(); + + public SpatialOsApplication SpatialOsApplication = new SpatialOsApplication(); + + public Unity Unity = new Unity(); + } + + [Serializable] + public class SpatialOsApplication + { + /// + /// The name of the assembly to use. There's no default value for this. + /// NOTE: This is only used for using the client against deployed games. + /// + public string AssemblyName; + + /// + /// The name of the deployment to connect to. + /// If empty, a local version of SpatialOS will be connected to. + /// + public string DeploymentId; + + /// + /// Used for fetching deployments with the given tag to allow the users to choose the shard to connect to. + /// NOTE: This is only used for running client for deployed games. + /// + public string DeploymentTag = "prod"; + + public WorkerPlatform WorkerPlatform = WorkerPlatform.UnityClient; + + /// + /// Login token used to talk to the Locator. There's no default values for this. + /// NOTE: This is only used for using the client against deployed games. + /// + public string LoginToken; + } + + [Serializable] + public class Unity + { + /// + /// Limit the number of entities that are created in a single frame. Tune this to avoid stalls during large checkouts. + /// + [Range(0, 10000000)] [Obsolete("Please use Improbable.Unity.Core.CountBasedSpawnLimiter and SpatialOS.EntitySpawnLimiter.")] + public int EntityCreationLimitPerFrame = Defaults.EntityCreationLimitPerFrame; + + /// + /// If true, downloaded AssetBundles will not be shared between multiple instances of Unity. + /// + [Obsolete("As of 12.1, this no longer does anything, and will be removed in a future release.")] + public bool UsePerInstanceAssetCache = Defaults.UsePerInstanceAssetCache; + + /// + /// If enabled, GameObjects will be allocated from a pool and re-used. + /// + /// + /// Care must be taken when writing MonoBehaviours (Visualizers) for use on pooled GameObjects. + /// All callbacks, local state and other data must be reset when the MonoBehaviour is disabled. + /// + public bool UsePrefabPooling; + } + + [Serializable] + public class Networking + { + public NetworkConnectionType LinkProtocol = Defaults.LinkProtocol; + + public string LocatorHost = Defaults.LocatorHost; + + public string ReceptionistHost = Defaults.ReceptionistHost; + + public ushort ReceptionistPort = Defaults.ReceptionistPort; + + public uint RaknetHeartbeatTimeoutMillis = Defaults.RaknetHeartbeatTimeoutMillis; + + public uint ReceiveQueueCapacity = Defaults.ReceiveQueueCapacity; + + public uint SendQueueCapacity = Defaults.SendQueueCapacity; + + public string SteamToken = null; + + public byte TcpMultiplexLevel = Defaults.TcpMultiplexLevel; + } + + [Serializable] + public class Debugging + { + public string InfraServiceUrl = Defaults.InfraServiceUrl; + + public bool LogDebugToSpatialOs = Defaults.LogDebugToSpatialOs; + + public bool LogAssertToSpatialOs = Defaults.LogAssertToSpatialOs; + + public bool LogWarningToSpatialOs = Defaults.LogWarningToSpatialOs; + + public bool LogErrorToSpatialOs = Defaults.LogErrorToSpatialOs; + + public bool LogExceptionToSpatialOs = Defaults.LogExceptionToSpatialOs; + + public bool ProtocolLoggingOnStartup = Defaults.ProtocolLoggingOnStartup; + + public uint ProtocolLogMaxFileBytes = Defaults.ProtocolLogMaxFileBytes; + + public string ProtocolLogPrefix = Defaults.ProtocolLogPrefix; + + [Obsolete("As of 12.1, this no longer does anything, and will be removed in a future release.")] + public bool ShowDebugTraces = Defaults.ShowDebugTraces; + + public bool UseInstrumentation = Defaults.UseInstrumentation; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfigurationData.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfigurationData.cs.meta new file mode 100644 index 0000000..b95e506 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Configuration/WorkerConfigurationData.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 34021fec446a5e14190f9556e2d8ce42 +timeCreated: 1470213215 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core.meta new file mode 100644 index 0000000..ad312a3 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b094315b693a80f42a2cbe6d6fe3dc85 +folderAsset: yes +timeCreated: 1444833351 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl.meta new file mode 100644 index 0000000..e852a70 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: ceabfe1aafcc05047b7d59adac704f8e +folderAsset: yes +timeCreated: 1479316120 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/Acl.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/Acl.cs new file mode 100644 index 0000000..bd365ac --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/Acl.cs @@ -0,0 +1,214 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; +using System.Collections.Generic; +using Improbable.Collections; +using Improbable.Worker; + +namespace Improbable.Unity.Core.Acls +{ + /// + /// Provides convenience methods for building up ACLs (Access Control Lists). + /// + public struct Acl + { + private static readonly HashSet allComponentIds = Dynamic.GetComponentIds(); + + private Map writePermissions; + private WorkerRequirementSet readPermissions; + + /// + /// Starts building a new ACL. + /// + public static Acl Build() + { + return new Acl(); + } + + /// + /// Gives workers that satisfy the given requirement set write permissions over the given component. + /// + /// + /// Calling this multiple times for the same component will overwrite the existing requirement set. + /// + public Acl SetWriteAccess(WorkerRequirementSet requirementSet) where TComponent : IComponentMetaclass + { + EnsureWritePermissionsAllocated(); + + var id = Dynamic.GetComponentId(); + writePermissions[id] = requirementSet; + return this; + } + + /// + /// Gives workers that satisfy the given requirement set write permissions over the given component. + /// + /// + /// Calling this multiple times for the same component will overwrite the existing requirement set. + /// + public Acl SetWriteAccess(uint componentId, WorkerRequirementSet requirementSet) + { + if (!allComponentIds.Contains(componentId)) + { + throw new InvalidOperationException(string.Format("{0} is an unknown component id", componentId)); + } + + EnsureWritePermissionsAllocated(); + writePermissions[componentId] = requirementSet; + + return this; + } + + /// + /// Gives workers that satisfy the given requirement set read permissions over the given component. + /// + /// + /// Calling this multiple times will overwrite the existing requirement set. + /// + public Acl SetReadAccess(WorkerRequirementSet requirementSet) + { + readPermissions = requirementSet; + return this; + } + + /// + /// Builds a new ACL component data object suitable for adding to a SnapshotEntity. + /// + /// + /// This can be called multiple times to create variants easily. + /// + public EntityAcl.Data ToData() + { + return new EntityAcl.Data(new EntityAclData(readPermissions, + writePermissions == null ? new Map() : writePermissions)); + } + + /// + /// Builds an ACL component update object suitable for sending in a component update for an existing entity. + /// + /// + /// This can be called multiple times to create variants easily. + /// + public EntityAcl.Update ToUpdate() + { + return new EntityAcl.Update().SetReadAcl(readPermissions).SetComponentWriteAcl( + writePermissions == null ? new Map() : writePermissions); + } + + /// + /// Creates a new starting with the given , and overwriting values in + /// 'otherAcl' with those present in 'newAcl'. + /// + public static Acl MergeIntoAcl(EntityAclData otherAcl, Acl newAcl) + { + Acl mergedAcl = new Acl(); + + mergedAcl.readPermissions = otherAcl.readAcl; + if (newAcl.readPermissions.attributeSet != null) + { + mergedAcl.readPermissions = newAcl.readPermissions; + } + + Map mergedWritePermissions = otherAcl.componentWriteAcl; + if (newAcl.writePermissions != null) + { + foreach (var key in newAcl.writePermissions.Keys) + { + mergedWritePermissions[key] = newAcl.writePermissions[key]; + } + } + + mergedAcl.writePermissions = mergedWritePermissions; + + return mergedAcl; + } + + /// + /// Creates an attribute set that a worker satisfies if and only if it has all of the attributes. + /// + public static WorkerAttributeSet MakeAttributeSet(string attribute1, params string[] attributes) + { + var list = new Collections.List(attributes.Length + 1); + foreach (var attribute in Enumerate(attribute1, attributes)) + { + list.Add(attribute); + } + + return new WorkerAttributeSet(list); + } + + /// + /// Creates a requirement set (a set of attribute sets) that a worker satisfies if and only + /// it satisfies at least one of the attribute sets. + /// + public static WorkerRequirementSet MakeRequirementSet(WorkerAttributeSet attribute1, params WorkerAttributeSet[] attributes) + { + var list = new Collections.List(attributes.Length + 1); + foreach (var attribute in Enumerate(attribute1, attributes)) + { + list.Add(attribute); + } + + return new WorkerRequirementSet(list); + } + + private static IEnumerable Enumerate(T element1, IEnumerable elements) + { + yield return element1; + + using (var enumerator = elements.GetEnumerator()) + { + while (enumerator.MoveNext()) + { + yield return enumerator.Current; + } + } + } + + private void EnsureWritePermissionsAllocated() + { + if (writePermissions == null) + { + writePermissions = new Map(); + } + } + + /// + /// Creates an ACL with with read permissions set to client or server, + /// and write permissions to server, for all components that exist on the given entity. + /// + public static Acl GenerateServerAuthoritativeAcl(Worker.Entity entity) + { + var acl = Acl.Build().SetReadAccess(CommonRequirementSets.PhysicsOrVisual); + foreach (var componentId in entity.GetComponentIds()) + { + acl.SetWriteAccess(componentId, CommonRequirementSets.PhysicsOnly); + } + + return acl; + } + + /// + /// Creates an ACL with read permissions set to client or server, + /// and write permissions to a client worker with the given worker ID, for all + /// components that exist on the given entity. + /// + public static Acl GenerateClientAuthoritativeAcl(Worker.Entity entity, string workerId) + { + if (string.IsNullOrEmpty(workerId)) + { + throw new ArgumentNullException("workerId"); + } + + var acl = Acl.Build().SetReadAccess(CommonRequirementSets.PhysicsOrVisual); + var specificClient = CommonRequirementSets.SpecificClientOnly(workerId); + foreach (var componentId in entity.GetComponentIds()) + { + acl.SetWriteAccess(componentId, specificClient); + } + + return acl; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/Acl.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/Acl.cs.meta new file mode 100644 index 0000000..7a22ea8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/Acl.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 562b1e96ea87bcd49a9a9b3a21a48b60 +timeCreated: 1479316120 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonAttributeSets.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonAttributeSets.cs new file mode 100644 index 0000000..4480249 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonAttributeSets.cs @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; + +namespace Improbable.Unity.Core.Acls +{ + /// + /// Provides commonly-used, well-known attribute sets. + /// + public static class CommonAttributeSets + { + /// + /// Identifies a physics worker. + /// + public static WorkerAttributeSet Physics = Acl.MakeAttributeSet("physics"); + + /// + /// Identifies a client worker. + /// + public static WorkerAttributeSet Visual = Acl.MakeAttributeSet("visual"); + + /// + /// Identifies a specific client worker. + /// + public static WorkerAttributeSet SpecificClient(string clientId) + { + if (string.IsNullOrEmpty(clientId)) + { + throw new ArgumentNullException("clientId"); + } + + return Acl.MakeAttributeSet(string.Format("workerId:{0}", clientId)); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonAttributeSets.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonAttributeSets.cs.meta new file mode 100644 index 0000000..79d4aab --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonAttributeSets.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0faf5b711b6513b46b0a620da7ed31c7 +timeCreated: 1479403713 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonClaims.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonClaims.cs new file mode 100644 index 0000000..b00de5b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonClaims.cs @@ -0,0 +1,37 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; + +namespace Improbable.Unity.Core.Acls +{ + /// + /// Provides commonly-used, well-known attribute sets. + /// + [Obsolete("Deprecated: Please use CommonAttributeSets instead.")] + public static class CommonClaims + { + /// + /// Identifies a physics worker. + /// + public static WorkerAttributeSet Physics = Acl.MakeAttributeSet("physics"); + + /// + /// Identifies a client worker. + /// + public static WorkerAttributeSet Visual = Acl.MakeAttributeSet("visual"); + + /// + /// Identifies a specific client worker. + /// + public static WorkerAttributeSet SpecificClient(string clientId) + { + if (string.IsNullOrEmpty(clientId)) + { + throw new ArgumentNullException("clientId"); + } + + return Acl.MakeAttributeSet(string.Format("workerId:{0}", clientId)); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonClaims.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonClaims.cs.meta new file mode 100644 index 0000000..e6b8e29 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonClaims.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9598b543b87d59f4883de0dd66ab9b7b +timeCreated: 1485969308 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonPredicates.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonPredicates.cs new file mode 100644 index 0000000..4d1a024 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonPredicates.cs @@ -0,0 +1,42 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; + +namespace Improbable.Unity.Core.Acls +{ + /// + /// Provides commonly-used, well-known requirement sets. + /// + [Obsolete("Deprecated: Please use CommonRequirementSets instead.")] + public static class CommonPredicates + { + /// + /// Only satisfied by a physics worker. + /// + public static WorkerRequirementSet PhysicsOnly = Acl.MakeRequirementSet(CommonAttributeSets.Physics); + + /// + /// Only satisfied by a visual worker. + /// + public static WorkerRequirementSet VisualOnly = Acl.MakeRequirementSet(CommonAttributeSets.Visual); + + /// + /// Satisfied by a physics or visual worker. + /// + public static WorkerRequirementSet PhysicsOrVisual = Acl.MakeRequirementSet(CommonAttributeSets.Physics, CommonAttributeSets.Visual); + + /// + /// Satisfied by a specific client only. + /// + public static WorkerRequirementSet SpecificClientOnly(string clientId) + { + if (string.IsNullOrEmpty(clientId)) + { + throw new ArgumentNullException("clientId"); + } + + return Acl.MakeRequirementSet(CommonAttributeSets.SpecificClient(clientId)); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonPredicates.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonPredicates.cs.meta new file mode 100644 index 0000000..9e0b385 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonPredicates.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9c355812e84737045ab2921b5e87e815 +timeCreated: 1485969308 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonRequirementSets.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonRequirementSets.cs new file mode 100644 index 0000000..9161164 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonRequirementSets.cs @@ -0,0 +1,41 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; + +namespace Improbable.Unity.Core.Acls +{ + /// + /// Provides commonly-used, well-known requirement sets. + /// + public static class CommonRequirementSets + { + /// + /// Only satisfied by a physics worker. + /// + public static WorkerRequirementSet PhysicsOnly = Acl.MakeRequirementSet(CommonAttributeSets.Physics); + + /// + /// Only satisfied by a visual worker. + /// + public static WorkerRequirementSet VisualOnly = Acl.MakeRequirementSet(CommonAttributeSets.Visual); + + /// + /// Satisfied by a physics or visual worker. + /// + public static WorkerRequirementSet PhysicsOrVisual = Acl.MakeRequirementSet(CommonAttributeSets.Physics, CommonAttributeSets.Visual); + + /// + /// Satisfied by a specific client only. + /// + public static WorkerRequirementSet SpecificClientOnly(string clientId) + { + if (string.IsNullOrEmpty(clientId)) + { + throw new ArgumentNullException("clientId"); + } + + return Acl.MakeRequirementSet(CommonAttributeSets.SpecificClient(clientId)); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonRequirementSets.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonRequirementSets.cs.meta new file mode 100644 index 0000000..5b3c1a9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/CommonRequirementSets.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d7c8e2fa51e39684598c42a7745bedeb +timeCreated: 1479403816 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/EntityExtensions.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/EntityExtensions.cs new file mode 100644 index 0000000..d6c3f3a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/EntityExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core.Acls +{ + /// + /// Convenience methods for managing entity snapshots. + /// + public static class EntityExtensions + { + /// + /// Adds an ACL component to an entity or overwrites an existing one. + /// + public static void SetAcl(this Worker.Entity entity, Acl acl) + { + if (entity.Get().HasValue) + { + entity.Update(acl.ToUpdate()); + return; + } + + entity.Add(acl.ToData()); + } + + /// + /// Merges an into the entity's existing set of ACLs. + /// + public static void MergeAcl(this Worker.Entity entity, Acl newAcl) + { + if (entity.Get().HasValue) + { + entity.Update(Acl.MergeIntoAcl(entity.Get().Value.Get().Value, newAcl).ToUpdate()); + return; + } + + entity.Add(newAcl.ToData()); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/EntityExtensions.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/EntityExtensions.cs.meta new file mode 100644 index 0000000..12e1c1b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Acl/EntityExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 05835ea961e5d8c4c97ddbdae5956be3 +timeCreated: 1479402870 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CloudAssemblyArtifactResolver.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CloudAssemblyArtifactResolver.cs new file mode 100644 index 0000000..4e8b81a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CloudAssemblyArtifactResolver.cs @@ -0,0 +1,209 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Util; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + class CloudAssemblyArtifactResolver + { + private readonly MonoBehaviour coroutineHost; + private readonly IWWWRequest wwwRequest; + private readonly string infraServiceUrl; + private readonly string projectName; + private readonly string assemblyName; + private readonly Action> onAssetsResolved; + private readonly Action onFailed; + private readonly TaskRunnerWithExponentialBackoff taskRunner; + private WWWRequestWrapper activeRequest; + + private enum State + { + WaitingToStart, + Started, + Cancelled, + Completed + } + + private State state; + + private CloudAssemblyArtifactResolver(MonoBehaviour coroutineHost, IWWWRequest wwwRequest, string infraServiceUrl, string projectName, string assemblyName, Action> onAssetsResolved, Action onFailed) + { + this.coroutineHost = coroutineHost; + this.wwwRequest = wwwRequest; + this.infraServiceUrl = infraServiceUrl; + this.projectName = projectName; + this.assemblyName = assemblyName; + this.onAssetsResolved = onAssetsResolved; + this.onFailed = onFailed; + + taskRunner = new TaskRunnerWithExponentialBackoff(); + + state = State.WaitingToStart; + } + + public void Cancel() + { + switch (state) + { + case State.WaitingToStart: + throw new Exception("Trying to cancel assembly artifact resolution before it has started."); + case State.Started: + state = State.Cancelled; + + activeRequest.Cancel(); + + taskRunner.ProcessResult(null); + break; + case State.Cancelled: + throw new Exception("This assembly artifact resolution was already cancelled."); + case State.Completed: + // Do nothing + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + public void Start() + { + Action runTask = () => + { + var headers = new Dictionary + { + { "Accept", "application/json" } + }; + var url = string.Format("{0}/assembly_content/v3/{1}/{2}/artifacts", infraServiceUrl, projectName, assemblyName); + + activeRequest = wwwRequest.SendPostRequest(coroutineHost, url, null, headers, (response) => + { + if (state == State.Cancelled) + { + // Ignore cancelled requests because the Cancel function will already call taskRunner.ProcessResult. + return; + } + + taskRunner.ProcessResult(response); + }); + + state = State.Started; + }; + Func evaluationFunc = response => + { + if (state == State.Cancelled) + { + // Return a success so it will finish running the task and will not be retried. + + return new TaskResult + { + IsSuccess = true + }; + } + + return new TaskResult + { + IsSuccess = string.IsNullOrEmpty(response.Error), + ErrorMessage = response.Error + }; + }; + Action onSuccess = response => + { + if (state == State.Cancelled) + { + // Do not call the onAssetsResolved or onFailed here, as the owner of this instance has initiated the request be cancelled. + return; + } + + state = State.Completed; + + var assetUrls = new Dictionary(); + var jsonResponse = JsonUtility.FromJson(response.Text); + if (jsonResponse.Artifacts.Count == 0) + { + onFailed(new Exception(string.Format("No artifacts found at {0}", response.Url))); + } + else + { + for (var i = 0; i < jsonResponse.Artifacts.Count; i++) + { + var artifact = jsonResponse.Artifacts[i]; + assetUrls[artifact.ArtifactId.Name] = artifact.Url; + } + + onAssetsResolved(assetUrls); + } + }; + Action onFailure = (string errorMessage) => + { + state = State.Completed; + + onFailed(new Exception("Failed to retrieve assembly list: " + errorMessage)); + }; + taskRunner.RunTaskWithRetries("CloudAssemblyArtifactResolver::ResolveAssetUrls", coroutineHost, runTask, evaluationFunc, onSuccess, onFailure); + } + + public static CloudAssemblyArtifactResolver ResolveAssetUrls(MonoBehaviour coroutineHost, IWWWRequest wwwRequest, string infraServiceUrl, string projectName, string assemblyName, Action> onAssetsResolved, Action onFailed) + { + var resolver = new CloudAssemblyArtifactResolver(coroutineHost, wwwRequest, infraServiceUrl, projectName, assemblyName, onAssetsResolved, onFailed); + + resolver.Start(); + + return resolver; + } + } + + [Serializable] + public class AssemblyResponse + { + [SerializeField] +#pragma warning disable 649 + private List artifacts; +#pragma warning restore 649 + public List Artifacts + { + get { return artifacts; } + set { artifacts = value; } + } + } + + [Serializable] + public class ArtifactId + { + [SerializeField] +#pragma warning disable 649 + private string name; +#pragma warning restore 649 + public string Name + { + get { return name; } + set { name = value; } + } + } + + [Serializable] + public class AssemblyArtifact + { + [SerializeField] +#pragma warning disable 649 + // ReSharper disable once InconsistentNaming + private ArtifactId artifact_id; +#pragma warning restore 649 + public ArtifactId ArtifactId + { + get { return artifact_id; } + set { artifact_id = value; } + } + + [SerializeField] +#pragma warning disable 649 + private string url; +#pragma warning restore 649 + public string Url + { + get { return url; } + set { url = value; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CloudAssemblyArtifactResolver.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CloudAssemblyArtifactResolver.cs.meta new file mode 100644 index 0000000..1800f8d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CloudAssemblyArtifactResolver.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e29f6d81fd0d26944a651954f663730b +timeCreated: 1469546112 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionCoroutineHost.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionCoroutineHost.cs new file mode 100644 index 0000000..b6d66f9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionCoroutineHost.cs @@ -0,0 +1,12 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// This class hosts coroutines for the ConnectLifecyle to ensure that all + /// unmanaged resources (futures, etc.) are freed during aborted connection attempts. + /// + class ConnectionCoroutineHost : MonoBehaviour { } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionCoroutineHost.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionCoroutineHost.cs.meta new file mode 100644 index 0000000..37345c5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionCoroutineHost.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8166aa3422ade8440bf95a57204cb43e +timeCreated: 1477933623 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionLifecycle.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionLifecycle.cs new file mode 100644 index 0000000..69d56d4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionLifecycle.cs @@ -0,0 +1,448 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections; +using System.Linq; +using Improbable.Unity.Configuration; +using Improbable.Unity.Entity; +using Improbable.Unity.Logging; +using Improbable.Unity.Util; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Manages the tasks required to take a worker through its bootstrap lifecycle. + /// + class ConnectionLifecycle : MonoBehaviour + { + private readonly System.Collections.Generic.List managedBehaviours = new System.Collections.Generic.List(); + private DispatchEventHandler eventHandler; + private WorkerConnectionParameters parameters; + private SpatialCommunicator spatialCommunicator; + private View view; + private Connection connection; + private IComponentCommander componentCommander; + private Commander commander; + + private IDeferredActionDispatcher deferredActionDispatcher; + private LegacyEntityPipelineSetup legacyEntityPipeline; + private LogSender logger; + private bool IsDisposed = false; + + public DispatchEventHandler DispatchEventHandler + { + get { return eventHandler; } + } + + public IUniverse Universe + { + get { return LocalEntities.Instance; } + } + + public Deployment? Deployment { get; private set; } + + public View View + { + get { return view; } + private set { view = value; } + } + + public Connection Connection + { + get { return connection; } + private set { connection = value; } + } + + public IComponentCommander ComponentCommander + { + get { return componentCommander; } + private set { componentCommander = value; } + } + + public Commander Commander + { + get { return commander; } + private set { commander = value; } + } + + private void Start() + { + PrintClientInfo(); + + parameters = CreateWorkerConnectionParameters(SpatialOS.Configuration); + + View = new View(); + View.OnDisconnect(SpatialOS.Disconnected); + + deferredActionDispatcher = new DeferredActionDispatcher(); + spatialCommunicator = new SpatialCommunicator(null, View, deferredActionDispatcher); + eventHandler = new DispatchEventHandler(this, spatialCommunicator); + + InitializeEntityPipeline(); + + // The current MonoBehaviour could be destroyed in response to aborted connection attempts, + // causing leaks of unmanaged resources due to in-progress coroutines not running to completion. + // Create a new host MonoBehaviour and run on it instead. + var starter = gameObject.GetComponent() ?? gameObject.AddComponent(); + starter.StartCoroutine(Connect()); + } + + private void InitializeEntityPipeline() + { + // Setup legacy entity pipeline if alternative hasn't been set up. + if (EntityPipeline.Internal.IsEmpty) + { + var config = + new LegacyEntityPipelineConfiguration(DispatchEventHandler.EntityInterestedComponentsUpdater, + DispatchEventHandler); + + var spawnLimiter = SpatialOS.EntitySpawnLimiter; + if (spawnLimiter == null) + { +#pragma warning disable 618 + if (config.EntityCreationLimitPerFrame > 0) + { + spawnLimiter = new CountBasedSpawnLimiter(config.EntityCreationLimitPerFrame); + } +#pragma warning restore 618 + else + { + spawnLimiter = new GreedySpawnLimiter(); + } + } + + legacyEntityPipeline = new LegacyEntityPipelineSetup(this /* Host MonoBehaviour */, + EntityPipeline.Instance, + spatialCommunicator, + LocalEntities.Internal, config, spawnLimiter); + + legacyEntityPipeline.Setup(); + } + + LocalEntities.Internal.Clear(); + } + + private IEnumerator Connect() + { + if (ShouldGetDeploymentList()) + { + var callback = SpatialOS.OnDeploymentListReceived ?? OnDeploymentListReceived; + var getList = WorkerConnection.GetDeploymentListAsync(parameters, callback, chosenDeployment => Deployment = chosenDeployment); + + yield return getList; + var listWait = new WaitUntil(() => Deployment.HasValue); + yield return listWait; + } + + // Can't start prepooling assets until a deployment is chosen, since the Streaming strategy needs to know which assembly it should stream assets from. + if (legacyEntityPipeline != null) + { + var prepareAssetsCoroutine = legacyEntityPipeline.PrepareAssets(); + if (prepareAssetsCoroutine != null) + { + yield return prepareAssetsCoroutine; + } + } + + var connect = WorkerConnection.ConnectAsync(parameters, Deployment, AttachConnection); + yield return connect; + + SpatialOS.ConnectionWasSuccesful = Connection.IsConnected; + if (SpatialOS.ConnectionWasSuccesful) + { + logger = new LogSender(() => SpatialOS.IsConnected, Connection.SendLogMessage, SpatialOS.Configuration, SpatialOS.LogFilter); + SetupComponents(); + + if (SpatialOS.Configuration.ProtocolLoggingOnStartup) + { + Connection.SetProtocolLoggingEnabled(true); + } + + eventHandler.ConnectionReady(); + + var componentCommander = new ComponentCommander(null, spatialCommunicator); + + Commander = new Commander(componentCommander, spatialCommunicator); + ComponentCommander = componentCommander; + + SpatialOS.OnConnectedInternal(); + } + } + + private void AttachConnection(Connection connectionToUse) + { + this.connection = connectionToUse; + spatialCommunicator.AttachConnection(connectionToUse); + } + + private static bool ShouldGetDeploymentList() + { + return !string.IsNullOrEmpty(SpatialOS.Configuration.LoginToken) || + !string.IsNullOrEmpty(SpatialOS.Configuration.SteamToken); + } + + private void Update() + { + ProcessEvents(); + eventHandler.ProcessEvents(); + + if (SpatialOS.Disconnecting) + { + Dispose(); + } + } + + // It is necessary to dispose in OnApplicationQuit() if possible. If we dispose at a later time, we risk getting + // errors because we interfere with Unity's scene teardown. + // (Unity will call OnDisable and OnDestroy on all gameobjects immediately rather than calling OnDisable on all objects first + // before calling the first OnDestroy). + private void OnApplicationQuit() + { + Dispose(); + } + + // It is possible to exit the application without OnApplicationQuit() being called. + // We risk errors because we interfere with Unity's scene teardown but we at least + // guarantee that we dispose all unmanaged memory. + private void OnDisable() + { + Dispose(); + } + + private void OnDeploymentListReceived(DeploymentList deployments, Action handleDeploymentChosen) + { + if (!string.IsNullOrEmpty(deployments.Error)) + { + throw new Exception(string.Format("Could not retrieve deployment list for project '{0}': {1}", SpatialOS.Configuration.ProjectName, deployments.Error)); + } + + if (deployments.Deployments.Count == 0) + { + throw new Exception(string.Format("Could not find any deployments in project '{0}'", SpatialOS.Configuration.ProjectName)); + } + + Deployment chosenDeployment; + if (string.IsNullOrEmpty(SpatialOS.Configuration.DeploymentId)) + { + // No deployment was specified, so pick the first one returned by the Locator. + chosenDeployment = deployments.Deployments.First(); + if (deployments.Deployments.Count > 1) + { + Debug.LogError(string.Format("Locator returned {0} deployments, picking first the first one ('{1}')", deployments.Deployments.Count, chosenDeployment.DeploymentName)); + } + } + else + { + // A deployment was specified, so we either find it or fail. + chosenDeployment = deployments.Deployments.Find(d => d.DeploymentName == SpatialOS.Configuration.DeploymentId); + if (chosenDeployment.Equals(default(Deployment))) + { + throw new Exception(string.Format("Could not find deployment '{0}' in project '{1}'", SpatialOS.Configuration.DeploymentId, SpatialOS.Configuration.ProjectName)); + } + } + + handleDeploymentChosen(chosenDeployment); + } + + private void OnApplicationFocus(bool isFocused) + { + if (!isFocused && !Application.runInBackground) + { + Debug.LogWarning("The application is being paused, as Application.runInBackground is false and the application is losing focus. This will cause connection to SpatialOS to be interrupted."); + } + } + + private void SetupComponents() + { + managedBehaviours.Add(gameObject.AddComponent()); + managedBehaviours.Add(gameObject.AddComponent()); + managedBehaviours.Add(gameObject.AddComponent()); + + if (SpatialOS.Configuration.UseInstrumentation) + { + managedBehaviours.Add(gameObject.AddComponent()); + managedBehaviours.Add(gameObject.AddComponent()); + } + } + + private void RemoveComponents() + { + for (var i = 0; i < managedBehaviours.Count; i++) + { + Destroy(managedBehaviours[i]); + } + } + + private static void PrintClientInfo() + { + if (SpatialOS.Configuration.WorkerPlatform == WorkerPlatform.UnityClient) + { + if (Application.isEditor == false) + { + Debug.LogFormat("ClientMetrics OS={0}.\nProcessor: type {1}, cores {2}.\nGraphics: id {3}, vendor {4}, name {5}, size {6}.\nMemory: {7}.", + SystemInfo.operatingSystem, + SystemInfo.processorType, + SystemInfo.processorCount, + SystemInfo.graphicsDeviceID, + SystemInfo.graphicsDeviceVendorID, + SystemInfo.graphicsDeviceName, + SystemInfo.graphicsMemorySize, + SystemInfo.systemMemorySize); + } + } + } + + private static WorkerConnectionParameters CreateWorkerConnectionParameters(WorkerConfiguration instance) + { + return new WorkerConnectionParameters + { + ConnectionParameters = CreateConnectionParameters(instance), + LocatorParameters = CreateLocatorParameters(instance), + LocatorHost = instance.LocatorHost, + ReceptionistHost = instance.ReceptionistHost, + ReceptionistPort = instance.ReceptionistPort, + WorkerId = instance.WorkerId, + }; + } + + private static LocatorParameters CreateLocatorParameters(WorkerConfiguration instance) + { + var credentials = string.IsNullOrEmpty(instance.SteamToken) ? LocatorCredentialsType.LoginToken : LocatorCredentialsType.Steam; + + var locatorParameters = new LocatorParameters + { + ProjectName = instance.ProjectName, + CredentialsType = credentials + }; + + switch (locatorParameters.CredentialsType) + { + case LocatorCredentialsType.LoginToken: + locatorParameters.LoginToken.Token = instance.LoginToken; + break; + case LocatorCredentialsType.Steam: + locatorParameters.Steam.DeploymentTag = instance.DeploymentTag; + locatorParameters.Steam.Ticket = instance.SteamToken; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return locatorParameters; + } + + private static ConnectionParameters CreateConnectionParameters(WorkerConfiguration instance) + { + var tcpParameters = new TcpNetworkParameters { MultiplexLevel = instance.TcpMultiplexLevel }; + var raknetParameters = new RakNetNetworkParameters { HeartbeatTimeoutMillis = instance.RaknetHeartbeatTimeoutMillis }; + + var parameters = new ConnectionParameters + { + Network = + { + ConnectionType = instance.LinkProtocol, + Tcp = tcpParameters, + RakNet = raknetParameters, + UseExternalIp = instance.UseExternalIp, + }, + WorkerType = WorkerTypeUtils.ToWorkerName(instance.WorkerPlatform), + BuiltInMetricsReportPeriodMillis = 2000, + EnableProtocolLoggingAtStartup = SpatialOS.Configuration.ProtocolLoggingOnStartup, + ProtocolLogging = + { + LogPrefix = SpatialOS.Configuration.ProtocolLogPrefix, + MaxLogFiles = 10, + MaxLogFileSizeBytes = SpatialOS.Configuration.ProtocolLogMaxFileBytes + }, + ReceiveQueueCapacity = instance.ReceiveQueueCapacity, + SendQueueCapacity = instance.SendQueueCapacity, + }; + return parameters; + } + + private void Dispose() + { + if (IsDisposed) + { + return; + } + + TryDisposeAndResetReference(ref legacyEntityPipeline); + TryDispose(EntityPipeline.Internal); + TryDisposeAndResetReference(ref eventHandler); + TryDisposeAndResetReference(ref view); + TryDisposeAndResetReference(ref connection); + TryDisposeAndResetReference(ref componentCommander); + TryDisposeAndResetReference(ref commander); + TryDisposeAndResetReference(ref deferredActionDispatcher); + TryDisposeAndResetReference(ref logger); + + try + { + RemoveComponents(); + } + catch (Exception e) + { + Debug.LogException(e); + } + + try + { + SpatialOS.Dispose(); + } + catch (Exception e) + { + Debug.LogException(e); + } + + IsDisposed = true; // This needs to happen before Destroy(this) because OnDisable() will be called as part of Destroy(). + Destroy(this); + } + + // This method is here to ensure that a code block containing Dispose() calls is fully executed. If a Dispose() throws + // we would otherwise prematurely terminate the code block execution skipping code that should have been executed. + private void TryDispose(T disposable) where T : IDisposable + { + try + { + disposable.Dispose(); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + + private void TryDisposeAndResetReference(ref T disposable) where T : IDisposable + { + if (!ReferenceEquals(disposable, null)) + { + TryDispose(disposable); + disposable = default(T); + } + } + + /// + /// Processes all events available in the pipeline. + /// + public void ProcessEvents() + { + // Process local deferred actions. + if (deferredActionDispatcher != null) + { + deferredActionDispatcher.ProcessEvents(); + } + + // Process remote events. + if (Connection != null) + { + using (var ops = Connection.GetOpList( /* timeoutMillis */ 0)) + { + View.Process(ops); + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionLifecycle.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionLifecycle.cs.meta new file mode 100644 index 0000000..e47078f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ConnectionLifecycle.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f0f17f9505d7d374e8e8ba4af0531007 +timeCreated: 1469546112 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CountBasedSpawnLimiter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CountBasedSpawnLimiter.cs new file mode 100644 index 0000000..20dcbe0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CountBasedSpawnLimiter.cs @@ -0,0 +1,42 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.Core +{ + /// + /// + /// Spawn at most the specified number of entities per frame. + /// + public class CountBasedSpawnLimiter : IEntitySpawnLimiter + { + private readonly int limit; + private int toAdd; + + public CountBasedSpawnLimiter(int limit) + { + if (limit <= 0) + { + throw new ArgumentOutOfRangeException("limit", string.Format("Limit must be at least 1 entity per frame. To disable rate limiting, please use {0}", typeof(GreedySpawnLimiter).FullName)); + } + + this.limit = limit; + Reset(); + } + + public bool CanSpawnEntity() + { + return toAdd > 0; + } + + public void EntityAdded(EntityId entityId) + { + toAdd--; + } + + public void Reset() + { + toAdd = limit; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CountBasedSpawnLimiter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CountBasedSpawnLimiter.cs.meta new file mode 100644 index 0000000..927f6a0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/CountBasedSpawnLimiter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e606040014c87d44ebca0e0dd46cdaf9 +timeCreated: 1518018288 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DeferredActionDispatcher.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DeferredActionDispatcher.cs new file mode 100644 index 0000000..167dd1e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DeferredActionDispatcher.cs @@ -0,0 +1,55 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Defers actions to be executed at a later time. + /// + public class DeferredActionDispatcher : IDeferredActionDispatcher + { + private Queue currentQueue = new Queue(); + private Queue emptyQueue = new Queue(); + + /// + public void DeferAction(Action action) + { + currentQueue.Enqueue(action); + } + + /// + public void ProcessEvents() + { + // Replace current queue with an empty one in case deferred + // actions place more actions in the queue; these need to happen + // on the next invocation of ProcessEvents() method. + var capturedQueue = currentQueue; + currentQueue = emptyQueue; + + // Process local events. + while (capturedQueue.Count > 0) + { + try + { + capturedQueue.Dequeue()(); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + + // Return the queue for reuse. + emptyQueue = capturedQueue; + } + + /// + public void Dispose() + { + currentQueue.Clear(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DeferredActionDispatcher.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DeferredActionDispatcher.cs.meta new file mode 100644 index 0000000..9ca997e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DeferredActionDispatcher.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f5505291d2fef3f4498dd1c4b737c955 +timeCreated: 1489496249 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DispatchEventHandler.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DispatchEventHandler.cs new file mode 100644 index 0000000..d206eaf --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DispatchEventHandler.cs @@ -0,0 +1,137 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Entity; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Responsible for responding to SDK callbacks. + /// + class DispatchEventHandler : + IDispatchEventHandler, + IDisposable, + IComponentInterestOverridesUpdateReceiver, + IInterestedComponentUpdaterProvider, + IAuthorityChangedReceiver + { + private readonly SpatialCommunicator spatialCommunicator; + private readonly AuthorityChangedNotifier authorityChangedNotifier; + + private readonly EntityComponentInterestOverridesUpdater entityInterestedComponentsUpdater; + private readonly List authorityChangedCallbacks = new List(); + + + internal DispatchEventHandler(MonoBehaviour hostBehaviour, SpatialCommunicator spatialCommunicator) + { + entityInterestedComponentsUpdater = new EntityComponentInterestOverridesUpdater(hostBehaviour, SpatialOS.WorkerComponentInterestModifier); + entityInterestedComponentsUpdater.AddUpdateReceiver(this); + + this.spatialCommunicator = spatialCommunicator; + + authorityChangedNotifier = new AuthorityChangedNotifier(LocalEntities.Instance); + authorityChangedNotifier.RegisterAuthorityChangedReceiver(this); + } + + internal void ConnectionReady() + { + EntityPipeline.Internal.RegisterComponentFactories(SpatialOS.Connection, SpatialOS.Dispatcher); + + SpatialOS.Dispatcher.OnLogMessage(OnLogMessage); + SpatialOS.Dispatcher.OnMetrics(OnMetrics); + + EntityPipeline.Instance.AddBlock(authorityChangedNotifier); + EntityPipeline.Internal.Start(spatialCommunicator); + } + + /// + /// Should be called regularly (usually every frame.) + /// + internal void ProcessEvents() + { + EntityPipeline.Internal.ProcessOps(); + } + + /// + public event OnAuthorityChangedCallback OnAuthorityChanged + { + add { authorityChangedCallbacks.Add(value); } + remove { authorityChangedCallbacks.Remove(value); } + } + + private void OnLogMessage(LogMessageOp op) + { + switch (op.Level) + { + case LogLevel.Debug: + Debug.Log(op.Message); + break; + case LogLevel.Info: + Debug.Log(op.Message); + break; + case LogLevel.Warn: + Debug.LogWarning(op.Message); + break; + case LogLevel.Error: + Debug.LogError(op.Message); + break; + case LogLevel.Fatal: + Debug.LogError(op.Message); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void OnMetrics(MetricsOp metrics) + { + SpatialOS.Connection.SendMetrics(metrics.Metrics); + } + + /// + public void Dispose() + { + authorityChangedNotifier.TryRemoveAuthorityChangedReceiver(this); + + entityInterestedComponentsUpdater.RemoveUpdateReceiver(this); + entityInterestedComponentsUpdater.Dispose(); + } + + /// + public void OnComponentInterestOverridesUpdated(EntityId entity, System.Collections.Generic.Dictionary interestOverrides) + { + spatialCommunicator.RegisterComponentInterest(entity, interestOverrides); + } + + /// + public IEntityComponentInterestOverridesUpdater GetEntityInterestedComponentsUpdater() + { + return entityInterestedComponentsUpdater; + } + + /// + public void AuthorityChanged(EntityId entityId, IComponentMetaclass componentId, Authority authority, object component) + { + spatialCommunicator.TriggerComponentAuthorityChanged(entityId, componentId, authority, component); + for (int i = 0; i < authorityChangedCallbacks.Count; ++i) + { + try + { + authorityChangedCallbacks[i](entityId, componentId, authority, component); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + internal IEntityComponentInterestOverridesUpdater EntityInterestedComponentsUpdater + { + get { return entityInterestedComponentsUpdater; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DispatchEventHandler.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DispatchEventHandler.cs.meta new file mode 100644 index 0000000..daf9192 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/DispatchEventHandler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a6c0c556a0e988443a2f36cd87066744 +timeCreated: 1469546112 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityAclExtensions.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityAclExtensions.cs new file mode 100644 index 0000000..2a6c8a0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityAclExtensions.cs @@ -0,0 +1,18 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// Exension methods to send Acl updates. + /// + public static class EntityAclExtensions + { + /// + /// Sends an update based on an Acl builder. + /// + public static void Send(this EntityAcl.Writer writer, Acls.Acl aclUpdate) + { + writer.Send(aclUpdate.ToUpdate()); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityAclExtensions.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityAclExtensions.cs.meta new file mode 100644 index 0000000..675bb44 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityAclExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 38841d3731301fb44bda44766b5315b3 +timeCreated: 1480089499 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries.meta new file mode 100644 index 0000000..11de52e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 6d98c466b77ebab4f8b8654dbdb2e637 +folderAsset: yes +timeCreated: 1479221879 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/ConstraintExtensions.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/ConstraintExtensions.cs new file mode 100644 index 0000000..83eeb4b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/ConstraintExtensions.cs @@ -0,0 +1,79 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using Improbable.Worker; +using Improbable.Worker.Query; + +namespace Improbable.Unity.Core.EntityQueries +{ + /// + /// Extension methods for building EntityQueries from a query constraint. + /// + public static class ConstraintExtensions + { + private static readonly HashSet allComponentIds = Dynamic.GetComponentIds(); + + /// + /// Creates an entity query which returns only entity IDs in the result set (component snapshots of entities will not + /// be included). + /// + public static EntityQuery ReturnOnlyEntityIds(this IConstraint constraint) + { + return new EntityQuery { Constraint = constraint, ResultType = Query.NoSnapshotResultType }; + } + + /// + /// Creates an entity query which returns entities along with component snapshots, for each specified component type, + /// in the result set. + /// + public static EntityQuery ReturnComponents(this IConstraint constraint, uint componentType, params uint[] componentTypes) + { + foreach (var type in Enumerate(componentType, componentTypes)) + { + if (!allComponentIds.Contains(type)) + { + throw new KeyNotFoundException(string.Format("{0} is an unknown component type", type)); + } + } + + return new EntityQuery + { + Constraint = constraint, + ResultType = new SnapshotResultType(new Collections.List(Enumerate(componentType, componentTypes))) + }; + } + + /// + /// Creates an entity query which returns entities along with all available component snapshots in the result set. + /// + public static EntityQuery ReturnAllComponents(this IConstraint constraint) + { + return new EntityQuery + { + Constraint = constraint, + ResultType = Query.AllSnapshotResultType + }; + } + + /// + /// Creates an entity query which returns only the number of entities in the result set. + /// + public static EntityQuery ReturnCount(this IConstraint constraint) + { + return new EntityQuery { Constraint = constraint, ResultType = Query.CountResultType }; + } + + private static IEnumerable Enumerate(T element1, IEnumerable elements) + { + yield return element1; + + var enumerator = elements.GetEnumerator(); + while (enumerator.MoveNext()) + { + yield return enumerator.Current; + } + + enumerator.Dispose(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/ConstraintExtensions.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/ConstraintExtensions.cs.meta new file mode 100644 index 0000000..c301c38 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/ConstraintExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 281d48f4e6faef64aa7124ced317562f +timeCreated: 1479295522 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/Query.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/Query.cs new file mode 100644 index 0000000..9809783 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/Query.cs @@ -0,0 +1,95 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; +using System.Collections.Generic; +using Improbable.Worker; +using Improbable.Worker.Query; + +namespace Improbable.Unity.Core.EntityQueries +{ + /// + /// Provides a convenient way to build SpatialOS entity queries. + /// + public static class Query + { + private static readonly HashSet allComponentIds = Dynamic.GetComponentIds(); + + public static readonly IResultType NoSnapshotResultType = new SnapshotResultType(new Collections.List()); + public static readonly IResultType AllSnapshotResultType = new SnapshotResultType(); + public static readonly IResultType CountResultType = new CountResultType(); + + /// + /// Creates a constraint that is satisfied if and only if all of the given constraints are satisfied. + /// + public static IConstraint And(IConstraint constraint1, IConstraint constraint2, params IConstraint[] constraints) + { + return new AndConstraint(Enumerate(constraint1, constraint2, constraints)); + } + + /// + /// Creates a constraint that is satisfied if and only if at least one of the given constraints is satisfied. + /// + public static IConstraint Or(IConstraint constraint1, IConstraint constraint2, params IConstraint[] constraints) + { + return new OrConstraint(Enumerate(constraint1, constraint2, constraints)); + } + + /// + /// Creates a constraint that is satisfied by an entity with the given entity ID. + /// + public static IConstraint HasEntityId(EntityId entityId) + { + return new EntityIdConstraint(entityId); + } + + /// + /// Creates a constraint that is satisfied by entities with the given component. + /// + /// The type of the required component. + public static IConstraint HasComponent() where TComponent : IComponentMetaclass + { + return new ComponentConstraint(Dynamic.GetComponentId()); + } + + /// + /// Creates a constraint that is satisfied by entities with the given component. + /// + public static IConstraint HasComponent(uint componentId) + { + if (!allComponentIds.Contains(componentId)) + { + throw new InvalidOperationException(string.Format("Unknown componentId {0}", componentId)); + } + + return new ComponentConstraint(componentId); + } + + /// + /// Creates a constraint that is satisfied by entities that are located in the specified sphere. + /// + public static IConstraint InSphere(double x, double y, double z, double radius) + { + if (radius <= 0.0) + { + throw new InvalidOperationException("Please specify a valid radius."); + } + + return new SphereConstraint(x, y, z, radius); + } + + private static IEnumerable Enumerate(IConstraint constraint1, IConstraint constraint2, IEnumerable constraints) + { + yield return constraint1; + yield return constraint2; + + using (var enumerator = constraints.GetEnumerator()) + { + while (enumerator.MoveNext()) + { + yield return enumerator.Current; + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/Query.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/Query.cs.meta new file mode 100644 index 0000000..2b0cd39 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/EntityQueries/Query.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1c9400e9ec2061a45b91256e9dcd7af1 +timeCreated: 1479129898 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/GreedySpawnLimiter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/GreedySpawnLimiter.cs new file mode 100644 index 0000000..c017922 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/GreedySpawnLimiter.cs @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// + /// Spawn as many entities per frame as there are available. + /// + public class GreedySpawnLimiter : IEntitySpawnLimiter + { + public bool CanSpawnEntity() + { + return true; + } + + public void EntityAdded(EntityId entityId) { } + + public void Reset() { } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/GreedySpawnLimiter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/GreedySpawnLimiter.cs.meta new file mode 100644 index 0000000..ad2a52b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/GreedySpawnLimiter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 736f65822eb308c4493d94d90708d709 +timeCreated: 1518018288 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IAuthorityChangedReceiver.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IAuthorityChangedReceiver.cs new file mode 100644 index 0000000..b67afff --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IAuthorityChangedReceiver.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Worker; + +namespace Improbable.Unity.Core +{ + /// + /// Object that can receive authority change notifications. + /// + public interface IAuthorityChangedReceiver + { + /// + /// Called when authority over a component of an entity changes. + /// + void AuthorityChanged(EntityId entityId, IComponentMetaclass componentId, Authority authority, object component); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IAuthorityChangedReceiver.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IAuthorityChangedReceiver.cs.meta new file mode 100644 index 0000000..aebb013 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IAuthorityChangedReceiver.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f534f290f88feed4ca0def5c466a4373 +timeCreated: 1494518600 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDeferredActionDispatcher.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDeferredActionDispatcher.cs new file mode 100644 index 0000000..8ff8d2f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDeferredActionDispatcher.cs @@ -0,0 +1,22 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.Core +{ + /// + /// Defers actions to be executed at a later time. + /// + public interface IDeferredActionDispatcher : IDisposable + { + /// + /// Defers the supplied action until the next call to ProcessEvents(). + /// + void DeferAction(Action action); + + /// + /// Invokes all deferred actions accumulated in the queue. + /// + void ProcessEvents(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDeferredActionDispatcher.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDeferredActionDispatcher.cs.meta new file mode 100644 index 0000000..9b14a05 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDeferredActionDispatcher.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5497cf6bb24b8284ca34824cc02e40e3 +timeCreated: 1489502113 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDispatchEventHandler.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDispatchEventHandler.cs new file mode 100644 index 0000000..3e17441 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDispatchEventHandler.cs @@ -0,0 +1,22 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Worker; + +namespace Improbable.Unity.Core +{ + /// + /// Exposes events to listen for incoming SpatialOS operations such as authority changes. + /// + public interface IDispatchEventHandler + { + /// + /// Subscribed callbacks will be invoked whenever authority changes for any entity/component pair checked out on this + /// worker. + /// + [Obsolete("Obsolete as of 10.4.0. Please use Improbable.Unity.Core.AuthorityChangedNotifier and Improbable.Unity.Core.EntityPipeline. (see docs)")] + event OnAuthorityChangedCallback OnAuthorityChanged; + } + + public delegate void OnAuthorityChangedCallback(EntityId entityId, IComponentMetaclass componentId, Authority authority, object component); +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDispatchEventHandler.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDispatchEventHandler.cs.meta new file mode 100644 index 0000000..48ebd3e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IDispatchEventHandler.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 28a5a6e07353147419e307d35272ce3f +timeCreated: 1484662999 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IEntitySpawnLimiter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IEntitySpawnLimiter.cs new file mode 100644 index 0000000..b6701c4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IEntitySpawnLimiter.cs @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// Implement this interface to customize how many entities the Unity SDK attempts to spawn per frame. + /// + public interface IEntitySpawnLimiter + { + /// + /// Return false to stop spawning entities for this frame. + /// + bool CanSpawnEntity(); + + /// + /// Called after an entity has been spawned. + /// + void EntityAdded(EntityId entityId); + + /// + /// Called before spawning begins for the current frame. + /// + void Reset(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IEntitySpawnLimiter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IEntitySpawnLimiter.cs.meta new file mode 100644 index 0000000..90aa23c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/IEntitySpawnLimiter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4acf4f8a0e64dd84cb6da02fcd15dc9f +timeCreated: 1518018288 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ISpatialCommunicator.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ISpatialCommunicator.cs new file mode 100644 index 0000000..f92ca85 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ISpatialCommunicator.cs @@ -0,0 +1,160 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Collections; +using Improbable.Worker; +using Improbable.Worker.Query; + +namespace Improbable.Unity.Core +{ + /// + /// Provides an interface between Unity classes such as the Commander and the generated component MonoBehaviours + /// and the classes used to communicate with SpatialOS: the Connection and the Dispatcher. + /// + public interface ISpatialCommunicator + { + /// + /// Send a component update. + /// + void SendComponentUpdate(EntityId entityId, IComponentUpdate update, bool legacyCallbackSemantics = false) where C : IComponentMetaclass; + + /// + /// Send a command response. + /// + void SendCommandResponse(RequestId> requestId, ICommandResponse response) where C : ICommandMetaclass, new(); + + /// + /// Register what components a given entity is interested in. + /// + void SendAuthorityLossImminentAcknowledgement(EntityId entityId) where C : IComponentMetaclass; + + /// + /// Send a command request. + /// + RequestId> SendCommandRequest(EntityId entityId, ICommandRequest request, Option timeout, CommandDelivery commandDelivery) where TCommand : ICommandMetaclass, new(); + + /// + /// Send a reserve entity ID request. + /// + RequestId SendReserveEntityIdRequest(Option timeout); + + /// + /// Send a reserve entity IDs request. + /// + RequestId SendReserveEntityIdsRequest(uint numberOfEntityIds, Option timeout); + + /// + /// Send a create entity request. + /// + RequestId SendCreateEntityRequest(Worker.Entity template, Option entityId, Option timeout); + + /// + /// Send a delete entity request. + /// + RequestId SendDeleteEntityRequest(EntityId entityId, Option timeout); + + /// + /// Send an entity query request. + /// + RequestId SendEntityQueryRequest(EntityQuery query, Option timeout); + + /// + /// Defer an action until next time ProcessEvents() is called. + /// + void Defer(Action action); + + /// + /// Register a callback to be invoked when a component is added. + /// + ulong RegisterAddComponent(Action> callback) where C : IComponentMetaclass; + + /// + /// Register a callback to be invoked when a component is removed. + /// + ulong RegisterRemoveComponent(Action callback) where C : IComponentMetaclass; + + /// + /// Register a callback to be invoked when a component is updated. + /// + ulong RegisterComponentUpdate(Action> callback) where C : IComponentMetaclass; + + /// + /// Register a callback to be invoked when a command request is received. + /// + ulong RegisterCommandRequest(Action> callback) where C : ICommandMetaclass, new(); + + /// + /// Register a callback to be invoked when authority for a component changes. + /// + ulong RegisterAuthorityChange(Action callback) where C : IComponentMetaclass; + + /// + /// Register a callback to be invoked whne a command response is received. + /// + ulong RegisterCommandResponse(Action> response) where TCommand : ICommandMetaclass, new(); + + /// + /// Register a callback to be invoked when a reserve entity ID response is received. + /// + ulong RegisterReserveEntityIdResponse(Action callback); + + /// + /// Register a callback to be invoked when a reserve entity IDs response is received. + /// + ulong RegisterReserveEntityIdsResponse(Action callback); + + /// + /// Register a callback to be invoked when a create entity response is received. + /// + ulong RegisterCreateEntityResponse(Action callback); + + /// + /// Register a callback to be invoked when a delete entity response is received. + /// + ulong RegisterDeleteEntityResponse(Action callback); + + /// + /// Register a callback to be invoked when an entity query response is received. + /// + ulong RegisterEntityQueryResponse(Action createOnResponseThunk); + + /// + /// Deregister a previously registered callback with the given callback key. + /// + void Remove(ulong callbackKey); + + /// + /// Register what components a given entity is interested in. + /// + void RegisterComponentInterest(EntityId entityId, System.Collections.Generic.Dictionary interestOverrides); + + /// + /// Called when authority over a component changes. + /// + event OnAuthorityChangedCallback ComponentAuthorityChanged; + + /// + /// Return the authority state of the worker over a given entity. + /// + Authority GetAuthority(EntityId writerEntityId, uint writerComponentId); + + #region Entity creation pipeline + + /// + /// Registers a callback for . + /// + ulong RegisterCriticalSection(Action criticalSection); + + /// + /// Registers a callback for . + /// + ulong RegisterAddEntity(Action addEntity); + + /// + /// Registers a callback for . + /// + ulong RegisterRemoveEntity(Action removeEntity); + + #endregion + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ISpatialCommunicator.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ISpatialCommunicator.cs.meta new file mode 100644 index 0000000..1116a0c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ISpatialCommunicator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7edeafe7660167c45a708326959e6adb +timeCreated: 1483529603 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/LegacyEntityPipelineConfiguration.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/LegacyEntityPipelineConfiguration.cs new file mode 100644 index 0000000..3958d23 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/LegacyEntityPipelineConfiguration.cs @@ -0,0 +1,79 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Entity; +using Improbable.Unity.Metrics; + +namespace Improbable.Unity.Core +{ +#pragma warning disable 618 + class LegacyEntityPipelineConfiguration : ILegacyEntityPipelineConfiguration + { + private readonly IEntityComponentInterestOverridesUpdater _entityComponentInterestOverridesUpdater; + private readonly IInterestedComponentUpdaterProvider interestedComponentUpdaterProvider; + + public LegacyEntityPipelineConfiguration(IEntityComponentInterestOverridesUpdater entityComponentInterestOverridesUpdater, IInterestedComponentUpdaterProvider interestedComponentUpdaterProvider) + { + this._entityComponentInterestOverridesUpdater = entityComponentInterestOverridesUpdater; + this.interestedComponentUpdaterProvider = interestedComponentUpdaterProvider; + } + + public IEntityTemplateProvider TemplateProvider + { + get { return SpatialOS.TemplateProvider; } + } + + public bool UsePrefabPooling + { + get { return SpatialOS.Configuration.UsePrefabPooling; } + } + + public IList AssetsToPrecache + { + get { return SpatialOS.AssetsToPrecache; } + } + + public IEnumerable> AssetsToPrePool + { + get { return SpatialOS.AssetsToPrePool; } + } + + public int MaxConcurrentPrecacheConnections + { + get { return SpatialOS.MaxConcurrentPrecacheConnections; } + } + + public Action OnPrecachingCompleted + { + get { return SpatialOS.OnPrecachingCompleted; } + } + + public Action OnPrecacheProgress + { + get { return SpatialOS.OnPrecacheProgress; } + } + + [Obsolete("Please use Improbable.Unity.Core.CountBasedSpawnLimiter and SpatialOS.EntitySpawnLimiter.")] + public int EntityCreationLimitPerFrame + { + get { return SpatialOS.Configuration.EntityCreationLimitPerFrame; } + } + + public WorkerMetrics Metrics + { + get { return SpatialOS.Metrics; } + } + + public IEntityComponentInterestOverridesUpdater EntityComponentInterestOverridesUpdater + { + get { return _entityComponentInterestOverridesUpdater; } + } + + public IInterestedComponentUpdaterProvider InterestedComponentUpdaterProvider + { + get { return interestedComponentUpdaterProvider; } + } + } +#pragma warning restore 618 +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/LegacyEntityPipelineConfiguration.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/LegacyEntityPipelineConfiguration.cs.meta new file mode 100644 index 0000000..aabdbe2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/LegacyEntityPipelineConfiguration.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a37464d92e6d7ef4a89a5088fc8057c6 +timeCreated: 1493305991 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math.meta new file mode 100644 index 0000000..9ae6d89 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 52d9d9d1a275a1049bb23dc924ed242d +folderAsset: yes +timeCreated: 1497007850 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3UnityExtension.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3UnityExtension.cs new file mode 100644 index 0000000..0db82d8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3UnityExtension.cs @@ -0,0 +1,31 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using UnityEngine; + +namespace Improbable.Unity.Common.Core.Math +{ + public static class Vector3UnityExtension + { + public static Vector3f ToSpatialVector3f(this Vector3 unityVector3) + { + return new Vector3f(unityVector3.x, unityVector3.y, unityVector3.z); + } + + public static Vector3d ToSpatialVector3d(this Vector3 unityVector3) + { + return new Vector3d(unityVector3.x, unityVector3.y, unityVector3.z); + } + + public static Coordinates ToSpatialCoordinates(this Vector3 unityVector3) + { + return new Coordinates(unityVector3.x, unityVector3.y, unityVector3.z); + } + + public static bool IsFinite(this Vector3 unityVector3) + { + return !float.IsInfinity(unityVector3.x) && !float.IsInfinity(unityVector3.y) && !float.IsInfinity(unityVector3.z) && + !float.IsNaN(unityVector3.x) && !float.IsNaN(unityVector3.y) && !float.IsNaN(unityVector3.z); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3UnityExtension.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3UnityExtension.cs.meta new file mode 100644 index 0000000..cf3570a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3UnityExtension.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 772c758976058a74499949e50df56f33 +timeCreated: 1505125292 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3dUnityExtension.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3dUnityExtension.cs new file mode 100644 index 0000000..27f1051 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3dUnityExtension.cs @@ -0,0 +1,18 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +namespace Improbable.Unity.Common.Core.Math +{ + public static class Vector3dUnityExtension + { + public static Vector3f ToSpatialVector3f(this Vector3d unityVector3d) + { + return new Vector3f((float) unityVector3d.x, (float) unityVector3d.y, (float) unityVector3d.z); + } + + public static Coordinates ToSpatialCoordinates(this Vector3d unityVector3d) + { + return new Coordinates(unityVector3d.x, unityVector3d.y, unityVector3d.z); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3dUnityExtension.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3dUnityExtension.cs.meta new file mode 100644 index 0000000..51594be --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/Math/Vector3dUnityExtension.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1b031620358b5d54b8b8591ac940ca44 +timeCreated: 1505125292 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ProjectDescriptor.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ProjectDescriptor.cs new file mode 100644 index 0000000..c718edf --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ProjectDescriptor.cs @@ -0,0 +1,51 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.IO; +using Improbable.Unity.Util; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Represents a subset of the information in a SpatialOS project's spatialos.json. + /// + public class ProjectDescriptor + { + public static readonly string ProjectDescriptorPath = PathUtil.Combine(Application.dataPath, "..", "..", "..", "spatialos.json"); + public static readonly string DefaultFieldValue = "invalid"; + +#pragma warning disable 649 + [SerializeField] private string name; +#pragma warning restore 649 + public string Name + { + get { return name; } + } + +#pragma warning disable 649 + // ReSharper disable once InconsistentNaming + [SerializeField] private string sdk_version; +#pragma warning restore 649 + public string SdkVersion + { + get { return sdk_version; } + } + + public ProjectDescriptor(string name, string sdkVersion) + { + this.name = name; + this.sdk_version = sdkVersion; + } + + public static ProjectDescriptor Load() + { + if (File.Exists(ProjectDescriptorPath)) + { + var descriptorText = File.ReadAllText(ProjectDescriptorPath); + return JsonUtility.FromJson(descriptorText); + } + + return new ProjectDescriptor(DefaultFieldValue, DefaultFieldValue); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ProjectDescriptor.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ProjectDescriptor.cs.meta new file mode 100644 index 0000000..c7563aa --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/ProjectDescriptor.cs.meta @@ -0,0 +1,12 @@ + fileFormatVersion: 2 +guid: 0c47aae26834a74438eeaf2b2517d134 +timeCreated: 1457003688 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SaveAndRestoreScene.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SaveAndRestoreScene.cs new file mode 100644 index 0000000..43849ee --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SaveAndRestoreScene.cs @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using UnityEngine.SceneManagement; + +namespace Improbable.Unity.Core +{ + struct SaveAndRestoreScene : IDisposable + { + private readonly Scene oldScene; + + public SaveAndRestoreScene(Scene newScene) + { + oldScene = SceneManager.GetActiveScene(); + SceneManager.SetActiveScene(newScene); + } + + public void Dispose() + { + SceneManager.SetActiveScene(oldScene); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SaveAndRestoreScene.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SaveAndRestoreScene.cs.meta new file mode 100644 index 0000000..e5bdb3d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SaveAndRestoreScene.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 16f0214885dd49744b7e2cd2bc6b57ed +timeCreated: 1491569009 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SpatialOS.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SpatialOS.cs new file mode 100644 index 0000000..92aed84 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SpatialOS.cs @@ -0,0 +1,722 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Configuration; +using Improbable.Unity.Entity; +using Improbable.Unity.Logging; +using Improbable.Unity.Metrics; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// This class provides an interface for customising a worker, connecting and disconnecting from SpatialOS. + /// + public static class SpatialOS + { + private static WorkerConfiguration configuration; + private static ConnectionLifecycle connectionLifecycle; + private static IEnumerable> assetsToPrePool; + private static IEntityTemplateProvider templateProvider; + + private static readonly List> onDisconnected = new List>(); + private static readonly List onConnected = new List(); + + private static readonly List onConnectionFailed = new List(); + private static readonly List> onConnectionFailedWithReason = new List>(); + + private static readonly WorkerComponentInterestOverriderImpl WorkerComponentInterestImpl; + + private static IList assetsToPrecache; + private static int maxConcurrentPrecacheConnections = 5; + private static Action onPrecachingCompleted; + private static Action onPrecacheProgress; + private static WorkerMetrics metrics; + private static DisconnectOp receivedDisconnectOp; + + private static ILogFilterReceiver logFilter; + private static IEntitySpawnLimiter entitySpawnLimiter; + + static SpatialOS() + { + ClientError.ExceptionCallback = Debug.LogException; + + // Set this up here so that users have flexibility in how they manage their global overrides before they've connected to SpatialOS. + WorkerComponentInterestImpl = new WorkerComponentInterestOverriderImpl + { + EntityObjects = LocalEntities.Instance + }; + } + + internal static bool ConnectionWasSuccesful { get; set; } + + /// + /// A callback that will be invoked when the deployment list has been retrieved from the Locator. + /// It is passed the list of deployments, and a callback it should call to connect to the chosen deployment. + /// The default callback chooses the deployment specified by the command line flag or the Unity UI; if not specified, + /// it chooses the first deployment in the list, and logs an error if the list contains more than one. + /// + public static Action> OnDeploymentListReceived; + + /// + /// A callback that will be invoked during the queuing process. To abort an ongoing connection attempt, it should + /// call SpatialOS.Disconnect(). + /// The default callback only aborts if the platform returns an error. + /// + public static Action OnQueueStatus; + + /// + /// Gets or sets the template provider. + /// for more details about why you may want to set a custom provider. + /// + /// + /// It is invalid to change this after Connect is called. + /// + public static IEntityTemplateProvider TemplateProvider + { + get { return templateProvider; } + set + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("TemplateProvider was set after Connect was called."); + } + + templateProvider = value; + } + } + + /// + /// Gets or sets a list of prefabs to pool, and how many instances to pre-allocate. + /// + /// + /// It is invalid to change this after Connect is called. + /// + public static IEnumerable> AssetsToPrePool + { + get { return assetsToPrePool; } + set + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("AssetsToPrePool was set after Connect was called."); + } + + assetsToPrePool = value; + } + } + + /// + /// A list of assets that should be downloaded before connection proceeds. + /// + public static IList AssetsToPrecache + { + get { return assetsToPrecache; } + set + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("AssetsToPrecache was set after Connect was called."); + } + + assetsToPrecache = value; + } + } + + /// + /// The maximum number of concurrent asset downloads. + /// + public static int MaxConcurrentPrecacheConnections + { + get { return maxConcurrentPrecacheConnections; } + set + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("MaxConcurrentPrecacheConnections was set after Connect was called."); + } + + maxConcurrentPrecacheConnections = value; + } + } + + /// + /// A callback that will be invoked when all assets have been downloaded. + /// + public static Action OnPrecachingCompleted + { + get { return onPrecachingCompleted; } + set + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("OnPrecachingCompleted was set after Connect was called."); + } + + onPrecachingCompleted = value; + } + } + + /// + /// A callback that is invoked after each asset starts to download. + /// + public static Action OnPrecacheProgress + { + get { return onPrecacheProgress; } + set + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("OnPrecacheProgress was set after Connect was called."); + } + + onPrecacheProgress = value; + } + } + + /// + /// Provides a view of all entities that exist on this worker. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + [Obsolete("Obsolete in 10.5.0. Migrate to use LocalEntities.Instance.")] + public static IUniverse Universe + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("Universe was accessed before Connect called."); + } + + return connectionLifecycle.Universe; + } + } + + internal static DispatchEventHandler DispatchEventHandler + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("DispatchEventHandler was accessed before Connect called."); + } + + return connectionLifecycle.DispatchEventHandler; + } + } + + /// + /// Provides access to the worker id. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + public static string WorkerId + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("WorkerId was accessed before Connect called."); + } + + return connectionLifecycle.Connection.GetWorkerId(); + } + } + + /// + /// Provides access to the worker attributes. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + public static List WorkerAttributes + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("WorkerAttributes was accessed before Connect called."); + } + + return connectionLifecycle.Connection.GetWorkerAttributes(); + } + } + + /// + /// Provides access to the configured worker. + /// + /// + /// ApplyConfiguration must be called before access to this property is valid. + /// + public static WorkerConfiguration Configuration + { + get + { + if (configuration == null) + { + throw new InvalidOperationException("Configuration was accessed before ApplyConfiguration called."); + } + + return configuration; + } + } + + /// + /// The currently-selected deployment, if any. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + public static Deployment? Deployment + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("Deployment was accessed before Connect was called."); + } + + return connectionLifecycle.Deployment; + } + } + + /// + /// Provides callbacks for events related to the connection and updates. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + public static View Dispatcher + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("Dispatcher used while not connected"); + } + + return connectionLifecycle.View; + } + } + + /// + /// The current metrics. + /// + public static WorkerMetrics Metrics + { + get { return metrics ?? (metrics = new WorkerMetrics()); } + set { metrics = value; } + } + + /// + /// Provides the ability to modify the component interest that is automatically calculated for this worker. + /// + public static IWorkerComponentInterestOverrider WorkerComponentInterestModifier + { + get { return WorkerComponentInterestImpl; } + } + + /// + /// Call this to apply any customisations to the Unity Worker. + /// + /// + /// It is invalid to call this after this.Connect is called. + /// + public static void ApplyConfiguration(WorkerConfigurationData configData, IList commandLineArguments = null) + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("ApplyConfiguration was called after Connect was called."); + } + + configuration = new WorkerConfiguration(configData, commandLineArguments); + } + + /// + /// Start the bootstrap process which results in connecting to SpatialOS. + /// + /// + /// The game object that acts as the point of initialisation for the Improbable Fabric connection + /// within the scene. + /// + /// + /// It is invalid to call this method before ApplyConfiguration has been called. + /// It is invalid to call this multiple times between matching calls to Disconnect. + /// + public static void Connect(GameObject gameObject) + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("Connect called while already connected."); + } + + if (TemplateProvider == null) + { + TemplateProvider = gameObject.GetComponent() ?? gameObject.AddComponent(); + } + + connectionLifecycle = gameObject.AddComponent(); + } + + /// + /// The connection to SpatialOS. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + public static Connection Connection + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("Connection used while not connected"); + } + + return connectionLifecycle.Connection; + } + } + + /// + /// Used to send worker commands. Only use if you cannot send a command from + /// a component, as you might send the same command from multiple workers. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + public static IComponentCommander SendWorkerCommand + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("SendWorkerCommand used while not connected"); + } + + return connectionLifecycle.ComponentCommander; + } + } + + /// + /// Send commands and entity queries from writers. + /// + public static ICommandSender Commands + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("Commands used while not connected"); + } + + return connectionLifecycle.Commander; + } + } + + /// + /// Send commands and entity queries when a component writer is not available. + /// + public static IWorkerCommandSender WorkerCommands + { + get + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("WorkerCommands used while not connected"); + } + + return connectionLifecycle.Commander; + } + } + + /// + /// True if a valid connection exists to SpatialOS. + /// + public static bool IsConnected + { + get { return connectionLifecycle != null && connectionLifecycle.Connection != null && connectionLifecycle.Connection.IsConnected; } + } + + /// + /// Call this to cleanly disconnect from SpatialOS. All entities that exist will be despawned. + /// + /// + /// It is invalid to call this method if Connect has not been called. + /// + public static void Disconnect() + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("Disconnect called while not connected."); + } + + receivedDisconnectOp = new DisconnectOp { Reason = "Disconnect was called by the user." }; + Disconnecting = true; + } + + /// + /// Processes messages from the SpatialOS connection and triggers callbacks. + /// This should be called periodically, typically every frame. + /// + /// + /// If a connection attempt has failed, call this method to receive log messages and disconnection events. + /// + public static void ProcessEvents() + { + if (connectionLifecycle != null) + { + connectionLifecycle.ProcessEvents(); + } + } + + /// + /// This event is triggered when a connection to SpatialOS has been successfully made. + /// + public static event Action OnConnected + { + add { onConnected.Add(value); } + remove { onConnected.Remove(value); } + } + + /// + /// This event is triggered when an attempted connection to SpatialOS has been unsuccessful. + /// + [Obsolete("This signature for the OnConnectionFailed is deprecated. It will be replaced by OnConnectionFailed(string) where the string gives the reason for the connection failure.")] + public static event Action OnConnectionFailed + { + add { onConnectionFailed.Add(value); } + remove { onConnectionFailed.Remove(value); } + } + + /// + /// This event is triggered when an attempted connection to SpatialOS has been unsuccessful. + /// + public static event Action OnConnectionFailedWithReason + { + add { onConnectionFailedWithReason.Add(value); } + remove { onConnectionFailedWithReason.Remove(value); } + } + + /// + /// This event is triggered when the connection to SpatialOS is terminated for any reason. + /// + /// + /// Once OnDisconnected is invoked, the SpatialOS object will not be usable until SpatialOS.Connect() is called again. + /// + public static event Action OnDisconnected + { + add { onDisconnected.Add(value); } + remove { onDisconnected.Remove(value); } + } + + /// + /// Returns the raw entity, if it is available on this worker, or null otherwise. + /// + /// + /// It is invalid to call this if Connect has not been called first. + /// + public static Worker.Entity GetLocalEntity(EntityId entityId) + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("GetLocalEntity called while not connected"); + } + + Worker.Entity entity; + if (Dispatcher.Entities.TryGetValue(entityId, out entity)) + { + return entity; + } + + return null; + } + + /// + /// Returns a component of the specified type, or null if either the entity or the component is not available on this + /// worker. + /// + /// + /// It is invalid to call this if Connect has not been called first. + /// + public static IComponentData GetLocalEntityComponent(EntityId entityId) where T : IComponentMetaclass + { + var entity = GetLocalEntity(entityId); + if (entity == null) + { + return null; + } + + var option = entity.Get(); + if (option.HasValue) + { + return option.Value; + } + + return null; + } + + /// + /// Set this to a function that can be used to control which log messages are sent to SpatialOS. + /// + /// Set this to a class that can be used to control if an entity should be spawned or not. + /// + /// + /// It is invalid to call this after this.Connect is called. + /// Use this to avoid spawning too many entities in a single frame. + /// (Backwards compatibility) By default, entities will be limited to a maximum number per frame, set by + /// . + /// To disable rate limiting, use . + /// To limit based on maximum frame time, use . + /// + public static ILogFilterReceiver LogFilter + { + get { return logFilter; } + set + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("LogFilter was set after Connect was called."); + } + + logFilter = value; + } + } + + /// + /// Set this to a class that can be used to control if an entity should be spawned or not. + /// + /// + /// It is invalid to call this after this.Connect is called. + /// Use this to avoid spawning too many entities in a single frame. + /// (Backwards compatibility) By default, entities will be limited to a maximum number per frame, set by + /// . + /// To disable rate limiting, use . + /// To limit based on maximum frame time, use . + /// You can also implement the interface to provide your own strategy. + /// + public static IEntitySpawnLimiter EntitySpawnLimiter + { + get { return entitySpawnLimiter; } + set + { + if (connectionLifecycle != null) + { + throw new InvalidOperationException("EntitySpawnLimiter was set after Connect was called."); + } + + entitySpawnLimiter = value; + } + } + + /// + /// Indicates that disconnection has been requested. Used internally to defer destruction of resources. + /// + internal static bool Disconnecting { get; private set; } + + internal static void OnConnectedInternal() + { + // Now that we're connected, wire up the invalidation handler so that changes to global interest overrides can be broadcast to entities. + WorkerComponentInterestImpl.InterestInvalidationHandler = connectionLifecycle.DispatchEventHandler.EntityInterestedComponentsUpdater.InvalidateEntity; + + var callbacks = onConnected.ToArray(); + foreach (var callback in callbacks) + { + try + { + callback(); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + internal static void OnConnectionFailedInternal() + { + var callbacks = onConnectionFailed.ToArray(); + foreach (var callback in callbacks) + { + try + { + callback(); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + internal static void OnConnectionFailedWithReasonInternal() + { + var callbacks = onConnectionFailedWithReason.ToArray(); + foreach (var callback in callbacks) + { + try + { + callback(receivedDisconnectOp.Reason); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + internal static void OnDisconnectedInternal() + { + // Protect against callbacks modifying the list. + var callbacks = onDisconnected.ToArray(); + for (var i = 0; i < callbacks.Length; i++) + { + try + { + callbacks[i](receivedDisconnectOp); + } + catch (Exception e) + { + Debug.LogException(e); + } + } + } + + internal static void Disconnected(DisconnectOp disconnectOp) + { + receivedDisconnectOp = disconnectOp; + Disconnecting = true; + } + + internal static void SignalDisconnection() + { + if (!ConnectionWasSuccesful) + { + OnConnectionFailedInternal(); + OnConnectionFailedWithReasonInternal(); + } + else + { + OnDisconnectedInternal(); + } + } + + internal static void Dispose() + { + if (connectionLifecycle == null) + { + throw new InvalidOperationException("Dispose called while not connected."); + } + + connectionLifecycle = null; + SignalDisconnection(); + + templateProvider = null; + Disconnecting = false; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SpatialOS.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SpatialOS.cs.meta new file mode 100644 index 0000000..5ec872c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/SpatialOS.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 184ad5c3b5c51804b8f9e96a6e90a345 +timeCreated: 1469440090 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/TimeBasedSpawnLimiter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/TimeBasedSpawnLimiter.cs new file mode 100644 index 0000000..13feb69 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/TimeBasedSpawnLimiter.cs @@ -0,0 +1,39 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.Core +{ + /// + /// + /// Spawn entities within per-frame a time budget. + /// + public class TimeBasedSpawnLimiter : IEntitySpawnLimiter + { + private readonly TimeSpan limit; + private DateTime startingTime; + + public TimeBasedSpawnLimiter(TimeSpan limit) + { + if (limit < TimeSpan.FromMilliseconds(1)) + { + throw new ArgumentOutOfRangeException("limit", string.Format("Limit must be at least 1 millisecond per frame. To disable rate limiting, please use {0}", typeof(GreedySpawnLimiter).FullName)); + } + + this.limit = limit; + Reset(); + } + + public bool CanSpawnEntity() + { + return DateTime.UtcNow.Subtract(startingTime) < limit; + } + + public void EntityAdded(EntityId entityId) { } + + public void Reset() + { + startingTime = DateTime.UtcNow; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/TimeBasedSpawnLimiter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/TimeBasedSpawnLimiter.cs.meta new file mode 100644 index 0000000..5ce3cd0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/TimeBasedSpawnLimiter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 35e0466db833bff45bd6f66fc6f02a5a +timeCreated: 1518018288 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/UnityExtension.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/UnityExtension.meta new file mode 100644 index 0000000..85649b8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/UnityExtension.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c95e45ef45da09344a9871d6ca58bdac +folderAsset: yes +timeCreated: 1505485121 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/UnityExtension/GameObjectAuthorityExtensions.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/UnityExtension/GameObjectAuthorityExtensions.cs new file mode 100644 index 0000000..b378358 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/UnityExtension/GameObjectAuthorityExtensions.cs @@ -0,0 +1,82 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Unity.Core; +using Improbable.Worker; + +namespace UnityEngine +{ + public static class GameObjectAuthorityExtensions + { + /// + /// Returns true if the GameObject has authority over the specified component. + /// + /// + /// If the GameObject is not a SpatialOS entity. + /// + [Obsolete("Deprecated: Please use \"GetAuthority() == Improbable.Worker.Authority.Authoritative || GetAuthority() == Improbable.Worker.Authority.AuthorityLossImminent\" instead.")] + public static bool HasAuthority(this GameObject obj) where T : IComponentMetaclass + { + var authority = obj.GetAuthority(); + return authority == Authority.Authoritative || authority == Authority.AuthorityLossImminent; + } + + /// + /// Returns true if the GameObject has authority over the specified component. + /// + /// + /// If the GameObject is not a SpatialOS entity. + /// + [Obsolete("Deprecated: Please use \"GetAuthority(componentId) == Improbable.Worker.Authority.Authoritative || GetAuthority(componentId) == Improbable.Worker.Authority.AuthorityLossImminent\" instead.")] + public static bool HasAuthority(this GameObject obj, uint componentId) + { + var authority = obj.GetAuthority(componentId); + return authority == Authority.Authoritative || authority == Authority.AuthorityLossImminent; + } + + /// + /// Returns the authority state of the GameObject over the specified component. + /// + /// + /// If the GameObject is not a SpatialOS entity, or the authority for this entity could not be accessed. + /// + public static Authority GetAuthority(this GameObject obj) where T : IComponentMetaclass + { + return obj.GetAuthority(Dynamic.GetComponentId()); + } + + /// + /// Returns the authority state of the GameObject over the specified component. + /// + /// + /// If the GameObject is not a SpatialOS entity, or the authority for this entity could not be accessed. + /// + public static Authority GetAuthority(this GameObject obj, uint componentId) + { + var entityObject = obj.GetSpatialOsEntity(); + if (entityObject == null) + { + throw new InvalidOperationException(string.Format("{0} is not a SpatialOS-related entity.", obj)); + } + + Improbable.Collections.Map authorityForComponentsOfEntity; + + if (!SpatialOS.Dispatcher.Authority.TryGetValue(entityObject.EntityId, out authorityForComponentsOfEntity)) + { + throw new InvalidOperationException( + string.Format("Authority information for entity {0} could not be accessed.", obj)); + } + + Authority authorityForComponent; + + if (!authorityForComponentsOfEntity.TryGetValue(componentId, out authorityForComponent)) + { + // It is possible that this worker cannot even see the component. + // That means it certainly is not authoritative over it. + authorityForComponent = Authority.NotAuthoritative; + } + + return authorityForComponent; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/UnityExtension/GameObjectAuthorityExtensions.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/UnityExtension/GameObjectAuthorityExtensions.cs.meta new file mode 100644 index 0000000..a8398a8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/UnityExtension/GameObjectAuthorityExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 28b51f7a5f85039498981aaad68b2675 +timeCreated: 1505725838 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnection.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnection.cs new file mode 100644 index 0000000..6a6765c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnection.cs @@ -0,0 +1,188 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// This class is responsible for managing the connection to SpatialOS. + /// + /// + /// All methods and properties must be called or accessed from the game thread. + /// + public class WorkerConnection + { + private const int ReturnImmediatelyMillis = 0; + + /// + /// True if a valid connection exists to SpatialOS. + /// + [Obsolete("Use SpatialOS.IsConnected instead.")] + public static bool IsConnected + { + get { return SpatialOS.IsConnected; } + } + + /// + /// The connection to SpatialOS. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + [Obsolete("Use SpatialOS.Connection instead.")] + public static Connection Connection + { + get { return SpatialOS.Connection; } + } + + /// + /// Provides callbacks for events related to the connection and updates. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + [Obsolete("Use SpatialOS.Dispatcher instead.")] + public static Dispatcher Dispatcher + { + get { return SpatialOS.Dispatcher; } + } + + /// + /// Returns the deployment selected for connection, or null if no deployment was selected. + /// + /// + /// It is invalid to access this property if Connect has not been called first. + /// + [Obsolete("Use SpatialOS.Deployment instead.")] + public static Deployment? Deployment + { + get { return SpatialOS.Deployment; } + } + + /// + /// Processes messages from the SpatialOS connection and triggers callbacks. + /// This should be called periodically, typically every frame. + /// + [Obsolete("Use SpatialOS.ProcessEvents instead.")] + public static void ProcessEvents() + { + SpatialOS.ProcessEvents(); + } + + /// + /// Call this to initiate a connection to SpatialOS, using settings specified in WorkerConfiguration. + /// + /// + /// Is it invalid to call this method if IsConnected is true. + /// + public static IEnumerator ConnectAsync(WorkerConnectionParameters parameters, Deployment? deployment, Action onConnection) + { + if (SpatialOS.Connection != null) + { + throw new InvalidOperationException("ConnectAsync called while already connected"); + } + + IEnumerator connect; + + if (deployment.HasValue) + { + connect = ConnectToLocatorAsync(parameters, deployment.Value, onConnection); + } + else + { + connect = ConnectToReceptionistAsync(parameters, onConnection); + } + + yield return connect; + } + + /// + /// Whether connected or not, this method indicates whether any resources can be disposed. + /// + [Obsolete] + public static bool CanDispose() + { + return false; + } + + /// + /// Call this to free all resources created by a call to Connect. + /// Every call to Connect should always have a matching call to Dispose. + /// + /// + /// It is invalid to call this method if Connect has not been called first. + /// Calling this method invalidates access to some properties on this class. + /// + [Obsolete] + public static void Dispose() { } + + /// + /// Returns a list of deployments associated with the current ProjectName. + /// + /// + /// A list of available deployments. + /// + /// + /// Is it invalid to call this method again before the listReceived callback is invoked. + /// + public static IEnumerator GetDeploymentListAsync(WorkerConnectionParameters parameters, Action> listReceived, Action handleDeploymentChosen) + { + var locator = CreateLocator(parameters); + var future = locator.GetDeploymentListAsync(); + + // ReSharper disable once AccessToDisposedClosure + var wait = new WaitUntil(() => future.Get(ReturnImmediatelyMillis).HasValue); + yield return wait; + + listReceived(future.Get(), handleDeploymentChosen); + + future.Dispose(); + locator.Dispose(); + } + + private static bool OnQueueStatusThunk(QueueStatus status) + { + if (SpatialOS.OnQueueStatus != null) + { + SpatialOS.OnQueueStatus(status); + } + + return !SpatialOS.Disconnecting; + } + + private static Locator CreateLocator(WorkerConnectionParameters parameters) + { + return new Locator(parameters.LocatorHost, parameters.LocatorParameters); + } + + private static IEnumerator ConnectToLocatorAsync(WorkerConnectionParameters parameters, Deployment deployment, Action onConnection) + { + var locator = CreateLocator(parameters); + var connectionFuture = locator.ConnectAsync(deployment.DeploymentName, parameters.ConnectionParameters, OnQueueStatusThunk); + + // ReSharper disable once AccessToDisposedClosure + var wait = new WaitUntil(() => connectionFuture.Get(ReturnImmediatelyMillis).HasValue); + yield return wait; + + onConnection(connectionFuture.Get()); + + connectionFuture.Dispose(); + locator.Dispose(); + } + + private static IEnumerator ConnectToReceptionistAsync(WorkerConnectionParameters parameters, Action onConnection) + { + var connectionFuture = Worker.Connection.ConnectAsync(parameters.ReceptionistHost, parameters.ReceptionistPort, parameters.WorkerId, parameters.ConnectionParameters); + + // ReSharper disable once AccessToDisposedClosure + var wait = new WaitUntil(() => connectionFuture.Get(ReturnImmediatelyMillis).HasValue); + yield return wait; + + onConnection(connectionFuture.Get()); + connectionFuture.Dispose(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnection.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnection.cs.meta new file mode 100644 index 0000000..7423ce7 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnection.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7671ef079ef60f545874c7ef0f2c698b +timeCreated: 1469805904 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnectionParameters.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnectionParameters.cs new file mode 100644 index 0000000..a9d6182 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnectionParameters.cs @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Worker; + +namespace Improbable.Unity.Core +{ + public class WorkerConnectionParameters + { + public ConnectionParameters ConnectionParameters; + public LocatorParameters LocatorParameters; + public string LocatorHost; + public string ReceptionistHost; + public ushort ReceptionistPort; + public string WorkerId; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnectionParameters.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnectionParameters.cs.meta new file mode 100644 index 0000000..e59e6d2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Core/WorkerConnectionParameters.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4e9dd085558e3184b9f1c119024171aa +timeCreated: 1469805904 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity.meta new file mode 100644 index 0000000..a24fb49 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: dcb4c1bb3203fc948aeace9ea2e24d8c +folderAsset: yes +timeCreated: 1515488809 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/AssetDatabaseTemplateProvider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/AssetDatabaseTemplateProvider.cs new file mode 100644 index 0000000..f9b8ae6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/AssetDatabaseTemplateProvider.cs @@ -0,0 +1,42 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Assets; +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// Wraps an IAssetDatabase to work as an IEntityTemplateProvider. + /// + public class AssetDatabaseTemplateProvider : IEntityTemplateProvider + { + private readonly IAssetDatabase assetDatabase; + + public AssetDatabaseTemplateProvider(IAssetDatabase assetDatabase) + { + this.assetDatabase = assetDatabase; + } + + public void PrepareTemplate(string prefabName, Action onSuccess, Action onError) + { + assetDatabase.LoadAsset(prefabName, _ => onSuccess(prefabName), onError); + } + + public void CancelAllTemplatePreparations() + { + assetDatabase.CancelAllLoads(); + } + + public GameObject GetEntityTemplate(string prefabName) + { + GameObject templateObject; + if (!assetDatabase.TryGet(prefabName, out templateObject)) + { + throw new MissingComponentException(string.Format("Prefab: {0} cannot be found.", prefabName)); + } + + return templateObject; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/AssetDatabaseTemplateProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/AssetDatabaseTemplateProvider.cs.meta new file mode 100644 index 0000000..e1c8994 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/AssetDatabaseTemplateProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0f0d10cd1f0971c4eafea83a3729268e +timeCreated: 1444833356 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ComponentEditorDataObject.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ComponentEditorDataObject.cs new file mode 100644 index 0000000..86e83cb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ComponentEditorDataObject.cs @@ -0,0 +1,177 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Text; +using Improbable.Unity.Util; + +namespace Improbable.Unity.CodeGeneration +{ + /// + /// Class for bridging SpatialOS component classes with component editors in Unity Editor. + /// + /// + public class ComponentEditorDataObject : IComponentEditorDataObject + where TComponent : class, ICanAttachEditorDataObject + { + private const int EditorLogSize = 20; + private const int EditorSpeedBuckets = 10; + + private const string UpdateLogFormat = "{0:HH:mm:ss}:{1}"; + private const string ComponentUpdateFormat = " {0}={1};"; + private const string CommandRequestFormat = "{0:HH:mm:ss}: {1}: {2}"; + + protected TComponent Component; + + private string[] componentUpdateLogArray = new string[EditorLogSize]; + private string[] commandRequestLogArray = new string[EditorLogSize]; + + private readonly CircularBuffer componentUpdateLog = new CircularBuffer(EditorLogSize); + private readonly CircularBuffer commandRequestLog = new CircularBuffer(EditorLogSize); + + private readonly CircularIntBuffer commandRequestsPerSecond = new CircularIntBuffer(EditorSpeedBuckets); + private readonly CircularIntBuffer componentUpdatesPerSecond = new CircularIntBuffer(EditorSpeedBuckets); + + private int componentUpdatesSinceLastUpdate; + private int commandRequestsSinceLastUpdate; + + internal double averageComponentUpdatesPerSecond; + internal double averageCommandRequestsPerSecond; + + private readonly StringBuilder fieldLogs = new StringBuilder(); + private bool updatePending; + + private DateTime lastUpdated = DateTime.MinValue; + private readonly TimeSpan updatePeriod = TimeSpan.FromSeconds(1); + + #region Unity Editor foldout status + +#pragma warning disable 649 + public bool ShowUpdates; + public bool ShowCommands; +#pragma warning restore 649 + + #endregion + + /// + /// Attaches SpatialOS generated component to this ComponentEditorDataObject. + /// + public void AttachComponent(TComponent component) + { + Component = component; + Component.AttachEditorDataObject(this); + } + + /// + /// Releases the reference to the component. + /// + public void DetachComponent() + { + Component.RemoveEditorDataObject(); + Component = null; + } + + /// + /// Called regularly to calculate wall time - based stats. + /// + public void UpdateEditorLogs() + { + var now = DateTime.UtcNow; + var timeSinceLastUpdate = now - lastUpdated; + if (now - lastUpdated < updatePeriod) + { + return; + } + + componentUpdatesPerSecond.Add((int) System.Math.Round(componentUpdatesSinceLastUpdate / timeSinceLastUpdate.TotalSeconds)); + componentUpdatesSinceLastUpdate = 0; + averageComponentUpdatesPerSecond = componentUpdatesPerSecond.GetAverage(); + + commandRequestsPerSecond.Add((int) System.Math.Round(commandRequestsSinceLastUpdate / timeSinceLastUpdate.TotalSeconds)); + commandRequestsSinceLastUpdate = 0; + averageCommandRequestsPerSecond = commandRequestsPerSecond.GetAverage(); + + lastUpdated = now; + } + + /// + /// Logs component update. + /// + /// + /// Component update will only propagate to the log when SendUpdateLog() is called. + /// + public void LogComponentUpdate(string componentName, object componentValue) + { + updatePending = true; + + fieldLogs.AppendFormat(ComponentUpdateFormat, componentName, componentValue); + } + + /// + /// Logs command request. + /// + /// + /// Component update will only propagate to the log when SendUpdateLog() is called. + /// + public void LogCommandRequest(DateTime dateTime, string commandName, object payload) + { + updatePending = true; + + var requestLog = string.Format(CommandRequestFormat, dateTime, commandName, payload); + commandRequestLog.Add(requestLog); + commandRequestLog.GetItemsInMostRecentOrder(ref commandRequestLogArray); + ++commandRequestsSinceLastUpdate; + } + + /// + /// Writes the combined update message to the log. + /// + public void SendUpdateLog() + { + if (!updatePending) + { + return; + } + + var updateLog = string.Format(UpdateLogFormat, DateTime.Now, fieldLogs); + componentUpdateLog.Add(updateLog); + componentUpdateLog.GetItemsInMostRecentOrder(ref componentUpdateLogArray); + ++componentUpdatesSinceLastUpdate; + + // Reuse fieldLogs. + fieldLogs.Length = 0; + updatePending = false; + } + + /// + /// Component update logs. + /// + public string[] ComponentUpdateLogArray + { + get { return componentUpdateLogArray; } + } + + /// + /// Command request logs. + /// + public string[] CommandRequestLogArray + { + get { return commandRequestLogArray; } + } + + /// + /// Average component updates per second. + /// + public double AverageComponentUpdatesPerSecond + { + get { return averageComponentUpdatesPerSecond; } + } + + /// + /// Average command requests per second. + /// + public double AverageCommandRequestsPerSecond + { + get { return averageCommandRequestsPerSecond; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ComponentEditorDataObject.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ComponentEditorDataObject.cs.meta new file mode 100644 index 0000000..8c25282 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ComponentEditorDataObject.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f3273d0942077c84c893ddc37a73226b +timeCreated: 1490268040 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/Containers.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/Containers.meta new file mode 100644 index 0000000..9852078 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/Containers.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 3b2b0b135f5bbc0499f1dee2a49bf901 +folderAsset: yes +timeCreated: 1510937553 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/Containers/EntityObjectDictionary.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/Containers/EntityObjectDictionary.cs new file mode 100644 index 0000000..3dfafed --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/Containers/EntityObjectDictionary.cs @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Improbable.Unity.Entity +{ + /// + /// Provides an efficient storage mechanism for EntityObjects which avoids the possibility of a + /// mismatch between an EntityId key and the associated EntityObject value. + /// + class EntityObjectDictionary : KeyedCollection + { + protected override EntityId GetKeyForItem(IEntityObject item) + { + return item.EntityId; + } + + public bool TryGetValue(EntityId entityId, out IEntityObject storage) + { + return Dictionary.TryGetValue(entityId, out storage); + } + + public IList Values + { + get { return Items; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/Containers/EntityObjectDictionary.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/Containers/EntityObjectDictionary.cs.meta new file mode 100644 index 0000000..69c4e73 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/Containers/EntityObjectDictionary.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b9ddcf26162a34646ad4a2e93b0cfc71 +timeCreated: 1510936807 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityBuilder.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityBuilder.cs new file mode 100644 index 0000000..9681126 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityBuilder.cs @@ -0,0 +1,145 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Common.Core.Math; +using Improbable.Unity.Core.Acls; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// Helper class for building an . Call to use. + /// + /// + /// EntityBuilder provides a builder for creating objects easily. Its helper + /// methods are structured such that all required components must be added before an entity can be + /// constructed. + /// + /// Helpers must be called in the following order: + /// , + /// , + /// , + /// , + /// . + /// After this point, all required components will have been set. More components can be added with + /// , and the can be built with . + /// You can also set write access on the ACL component using the optional + /// . + /// + /// This class is not thread-safe. + /// + /// + /// This example shows how to construct a simple entity using the EntityBuilder helpers. + /// + /// var entity = EntityBuilder.Begin() + /// .AddPositionComponent(position, positionWriteAcl) + /// .AddMetadataComponent(prefabName) + /// .SetPersistence(persistence) + /// .SetReadAcl(readAcl) + /// .AddComponent<MyComponent>(myComponent.Data, myComponentWriteAcl) + /// .Build(); + /// + /// + public class EntityBuilder : + IPositionAdder, + IMetadataAdder, + IPersistenceSetter, + IReadAclSetter, + IComponentAdder + { + private Worker.Entity entity; + private Acl entityAcl; + private bool hasBuiltOnce; + + /// + /// Returns a new . Start by calling . + /// + public static IPositionAdder Begin() + { + return new EntityBuilder(); + } + + protected EntityBuilder() + { + entity = new Worker.Entity(); + entityAcl = new Acl(); + } + + /// + public IMetadataAdder AddPositionComponent(Vector3 position, WorkerRequirementSet writeAcl) + { + entity.Add(new Position.Data(position.ToSpatialCoordinates())); + entityAcl.SetWriteAccess(writeAcl); + return this; + } + + /// + public IMetadataAdder AddPositionComponent(Vector3d position, WorkerRequirementSet writeAcl) + { + entity.Add(new Position.Data(position.ToSpatialCoordinates())); + entityAcl.SetWriteAccess(writeAcl); + return this; + } + + /// + public IMetadataAdder AddPositionComponent(Coordinates position, WorkerRequirementSet writeAcl) + { + entity.Add(new Position.Data(position)); + entityAcl.SetWriteAccess(writeAcl); + return this; + } + + /// + public IPersistenceSetter AddMetadataComponent(string entityType) + { + entity.Add(new Metadata.Data(entityType)); + return this; + } + + /// + public IReadAclSetter SetPersistence(bool persistence) + { + if (persistence) + { + entity.Add(new Persistence.Data()); + } + + return this; + } + + /// + public IComponentAdder SetReadAcl(WorkerRequirementSet readAcl) + { + entityAcl.SetReadAccess(readAcl); + return this; + } + + /// + public IComponentAdder SetEntityAclComponentWriteAccess(WorkerRequirementSet writeAcl) + { + entityAcl.SetWriteAccess(writeAcl); + return this; + } + + /// + public IComponentAdder AddComponent(IComponentData data, WorkerRequirementSet writeAcl) where C : IComponentMetaclass + { + entity.Add(data); + entityAcl.SetWriteAccess(writeAcl); + return this; + } + + /// + public Worker.Entity Build() + { + if (hasBuiltOnce) + { + throw new System.InvalidOperationException("Cannot call Build() multiple times on an EntityBuilder"); + } + + hasBuiltOnce = true; + entity.Add(entityAcl.ToData()); + return entity; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityBuilder.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityBuilder.cs.meta new file mode 100644 index 0000000..f7176e6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityBuilder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 00dd92eb51d304c4e848ec943d76d3d6 +timeCreated: 1498151275 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponentInterestOverridesUpdater.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponentInterestOverridesUpdater.cs new file mode 100644 index 0000000..9fc59e1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponentInterestOverridesUpdater.cs @@ -0,0 +1,179 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections; +using System.Collections.Generic; +using Improbable.Unity.Entity; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity +{ + /// + /// + /// Reports interested component overrides to SpatialOS. + /// + class EntityComponentInterestOverridesUpdater : IEntityComponentInterestOverridesUpdater, IDisposable + { + private readonly MonoBehaviour coroutineHostBehaviour; + private readonly IWorkerComponentInterestOverrider interestOverrider; + private Coroutine updateCoroutine; + private readonly HashSet entitiesToBeUpdated; + private readonly IDictionary> updatesSentCache; + private readonly IList updateReceiversList; + private static readonly HashSet EmptyComponentSet = new HashSet(); + + public EntityComponentInterestOverridesUpdater(MonoBehaviour coroutineHostBehaviour, IWorkerComponentInterestOverrider interestOverrider) + { + entitiesToBeUpdated = new HashSet(); + updatesSentCache = new Dictionary>(); + updateReceiversList = new List(); + this.coroutineHostBehaviour = coroutineHostBehaviour; + this.interestOverrider = interestOverrider; + } + + /// + public void InvalidateEntity(IEntityObject entityObject) + { + entitiesToBeUpdated.Add(entityObject); + ScheduleInterestedComponentsUpdate(); + } + + /// + public void RemoveEntity(IEntityObject entityObject) + { + entitiesToBeUpdated.Remove(entityObject); + updatesSentCache.Remove(entityObject); + } + + /// + public void AddUpdateReceiver(IComponentInterestOverridesUpdateReceiver updateReceiver) + { + updateReceiversList.Add(updateReceiver); + } + + /// + public void RemoveUpdateReceiver(IComponentInterestOverridesUpdateReceiver updateReceiver) + { + updateReceiversList.Remove(updateReceiver); + } + + /// + public void Dispose() + { + if (updateCoroutine != null) + { + coroutineHostBehaviour.StopCoroutine(updateCoroutine); + updateCoroutine = null; + } + + entitiesToBeUpdated.Clear(); + updateReceiversList.Clear(); + } + + /// + /// Wait until the end of the current frame and send an InterestedComponent update once per frame per entity. + /// + private void ScheduleInterestedComponentsUpdate() + { + if (updateCoroutine == null) + { + updateCoroutine = coroutineHostBehaviour.StartCoroutine(UpdateInterestedComponentsCoroutine()); + } + } + + private IEnumerator UpdateInterestedComponentsCoroutine() + { + yield return new WaitForEndOfFrame(); + UpdateInterestedComponents(); + updateCoroutine = null; + } + + internal void UpdateInterestedComponents() + { + if (entitiesToBeUpdated.Count == 0) + { + return; + } + + foreach (var entityObject in entitiesToBeUpdated) + { + var interestedComponents = new HashSet(entityObject.Components.RegisteredComponents.Keys); + var interestedComponentsVisualizers = entityObject.Visualizers.RequiredComponents; + + // Apply calculated component interest + foreach (var visualizerComponent in interestedComponentsVisualizers) + { + interestedComponents.Add(visualizerComponent); + } + + // Then apply user overrides to component interest. + using (var enumerator = interestOverrider.GetComponentInterest(entityObject.EntityId)) + { + while (enumerator.MoveNext()) + { + ApplyComponentInterest(enumerator.Current.Key, enumerator.Current.Value, interestedComponents); + } + } + + if (!EntityNeedsUpdate(entityObject, interestedComponents)) + { + continue; + } + + // Now convert to the CoreSdk's format. + var cache = updatesSentCache.ContainsKey(entityObject) ? updatesSentCache[entityObject] : EmptyComponentSet; + var interestOverrides = new Dictionary(); + foreach (var interestedComponent in interestedComponents) + { + if (!cache.Contains(interestedComponent)) + { + interestOverrides.Add(interestedComponent, new InterestOverride { IsInterested = true }); + } + } + + foreach (var cachedInterestedComponent in cache) + { + if (!interestedComponents.Contains(cachedInterestedComponent)) + { + interestOverrides.Add(cachedInterestedComponent, new InterestOverride { IsInterested = false }); + } + } + + // Then trigger callbacks. + for (int i = 0; i < updateReceiversList.Count; i++) + { + updateReceiversList[i].OnComponentInterestOverridesUpdated(entityObject.EntityId, interestOverrides); + } + + // Finally, cache the results. + updatesSentCache[entityObject] = interestedComponents; + } + + entitiesToBeUpdated.Clear(); + } + + private static void ApplyComponentInterest(uint componentId, WorkerComponentInterest interest, HashSet interestedComponents) + { + switch (interest) + { + case WorkerComponentInterest.Default: + Debug.LogErrorFormat("Internal logic error: {0} is not valid for component interest.", Enum.GetName(typeof(WorkerComponentInterest), WorkerComponentInterest.Default)); + break; + case WorkerComponentInterest.Always: + interestedComponents.Add(componentId); + break; + case WorkerComponentInterest.Never: + interestedComponents.Remove(componentId); + break; + default: + throw new ArgumentOutOfRangeException("interest", interest, null); + } + } + + private bool EntityNeedsUpdate(IEntityObject entity, HashSet components) + { + return !updatesSentCache.ContainsKey(entity) || !updatesSentCache[entity].SetEquals(components); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponentInterestOverridesUpdater.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponentInterestOverridesUpdater.cs.meta new file mode 100644 index 0000000..08c16a4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponentInterestOverridesUpdater.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5814fbed48b65b845a0d2f7548011fde +timeCreated: 1496307048 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponents.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponents.cs new file mode 100644 index 0000000..36597a8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponents.cs @@ -0,0 +1,70 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.CodeGeneration; +using Improbable.Unity.Entity; + +namespace Improbable.Unity +{ + class EntityComponents : IEntityComponents + { + private readonly IList invalidatorsList; + + /// + public IDictionary RegisteredComponents { get; private set; } + + /// + /// Creates a new instance of EntityComponents. + /// + public EntityComponents() + { + invalidatorsList = new List(); + RegisteredComponents = new Dictionary(); + } + + /// + public void RegisterInterestedComponent(uint componentId, ISpatialOsComponentInternal component) + { + if (RegisteredComponents.ContainsKey(componentId)) + { + throw new InvalidOperationException("Trying to add duplicate componentId to InterestedComponents"); + } + + RegisteredComponents[componentId] = component; + TriggerInterestedComponentsPotentiallyChanged(); + } + + /// + public void DeregisterInterestedComponent(uint componentId) + { + if (!RegisteredComponents.ContainsKey(componentId)) + { + throw new InvalidOperationException("Trying to remove non-existing componentId from InterestedComponents"); + } + + RegisteredComponents.Remove(componentId); + TriggerInterestedComponentsPotentiallyChanged(); + } + + /// + public void AddInvalidator(IEntityInterestedComponentsInvalidator invalidator) + { + invalidatorsList.Add(invalidator); + } + + /// + public void RemoveInvalidator(IEntityInterestedComponentsInvalidator invalidator) + { + invalidatorsList.Remove(invalidator); + } + + private void TriggerInterestedComponentsPotentiallyChanged() + { + for (int i = 0; i < invalidatorsList.Count; i++) + { + invalidatorsList[i].OnInterestedComponentsPotentiallyChanged(); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponents.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponents.cs.meta new file mode 100644 index 0000000..242d35e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityComponents.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 491fb318c0d8c2940a58766149cb03e5 +timeCreated: 1484563915 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObject.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObject.cs new file mode 100644 index 0000000..9d531af --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObject.cs @@ -0,0 +1,156 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Unity.Core; +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// Contains information about a SpatialOS entity and its associated GameObject. + /// + class EntityObject : IEntityObject, IEntityInterestedComponentsInvalidator, IDisposable + { + private readonly IInterestedComponentUpdaterProvider interestedComponentUpdaterProvider; + + /// + /// Creates a new instance of EntityObject. + /// + /// The associated EntityId. + /// The GameObject associated with the Entity. + /// The prefab name associated with the Entity. + /// + /// IInterestedComponentUpdaterProvider for callign + /// OnInterestedComponentsPotentiallyChanged(). + /// + public EntityObject(EntityId entityId, GameObject underlyingGameObject, string prefabName, IInterestedComponentUpdaterProvider interestedComponentUpdaterProvider) + { + this.interestedComponentUpdaterProvider = interestedComponentUpdaterProvider; + + if (underlyingGameObject == null) + { + throw new ArgumentNullException("underlyingGameObject"); + } + + EntityId = entityId; + UnderlyingGameObject = underlyingGameObject; + PrefabName = prefabName; + + Visualizers = new EntityVisualizers(UnderlyingGameObject); + Components = new EntityComponents(); + Visualizers.AddInvalidator(this); + Components.AddInvalidator(this); + } + + /// + public IEntityVisualizers Visualizers { get; private set; } + + /// + public IEntityComponents Components { get; private set; } + + /// + public string PrefabName { get; private set; } + + /// + public GameObject UnderlyingGameObject { get; private set; } + + /// + public EntityId EntityId { get; private set; } + + /// + public void OnInterestedComponentsPotentiallyChanged() + { + interestedComponentUpdaterProvider.GetEntityInterestedComponentsUpdater().InvalidateEntity(this); + } + + /// + public void Dispose() + { + Visualizers.RemoveInvalidator(this); + ((EntityVisualizers) Visualizers).Dispose(); + Components.RemoveInvalidator(this); + + if (UnderlyingGameObject != null) + { + UnderlyingGameObject.GetComponent().Clear(); + } + } + + public override string ToString() + { + return string.Format("Entity: {0}, id: {1} prefab: {2}", UnderlyingGameObject.name, EntityId, PrefabName); + } + + /// + public void OnAddComponentPipelineOp(AddComponentPipelineOp op) + { + if (op.EntityId != EntityId) + { + Debug.LogError(string.Format("EntityObject::OnAddComponentPipelineOp: Entity {0} received pipeline op for wrong entity id {1}.", EntityId, op.EntityId)); + return; + } + + var componentId = op.ComponentMetaClass.ComponentId; + if (!Components.RegisteredComponents.ContainsKey(componentId)) + { + return; + } + + Components.RegisteredComponents[componentId].OnAddComponentPipelineOp(op); + } + + /// + public void OnRemoveComponentPipelineOp(RemoveComponentPipelineOp op) + { + if (op.EntityId != EntityId) + { + Debug.LogError(string.Format("EntityObject::OnRemoveComponentPipelineOp: Entity {0} received pipeline op for wrong entity id {1}.", EntityId, op.EntityId)); + return; + } + + var componentId = op.ComponentMetaClass.ComponentId; + if (!Components.RegisteredComponents.ContainsKey(componentId)) + { + return; + } + + Components.RegisteredComponents[componentId].OnRemoveComponentPipelineOp(op); + } + + /// + public void OnComponentUpdatePipelineOp(UpdateComponentPipelineOp op) + { + if (op.EntityId != EntityId) + { + Debug.LogError(string.Format("EntityObject::OnComponentUpdatePipelineOp: Entity {0} received pipeline op for wrong entity id {1}.", EntityId, op.EntityId)); + return; + } + + var componentId = op.ComponentMetaClass.ComponentId; + if (!Components.RegisteredComponents.ContainsKey(componentId)) + { + return; + } + + Components.RegisteredComponents[componentId].OnComponentUpdatePipelineOp(op); + } + + /// + public void OnAuthorityChangePipelineOp(ChangeAuthorityPipelineOp op) + { + if (op.EntityId != EntityId) + { + Debug.LogError(string.Format("EntityObject::OnAuthorityChangePipelineOp: Entity {0} received pipeline op for wrong entity id {1}.", EntityId, op.EntityId)); + return; + } + + var componentId = op.ComponentMetaClass.ComponentId; + if (!Components.RegisteredComponents.ContainsKey(componentId)) + { + return; + } + + Components.RegisteredComponents[componentId].OnAuthorityChangePipelineOp(op); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObject.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObject.cs.meta new file mode 100644 index 0000000..cbf8b17 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObject.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1d94719050bfb5045b1e8a7beb0187fe +timeCreated: 1517582613 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObjectStorage.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObjectStorage.cs new file mode 100644 index 0000000..b6de1d6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObjectStorage.cs @@ -0,0 +1,53 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Unity.CodeGeneration; +using Improbable.Unity.Core; +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// Used to associate an Unity Object with our EntityObject. + /// + public class EntityObjectStorage : MonoBehaviour + { + /// + /// The associated EntityObject. + /// + public IEntityObject Entity { get; private set; } + + public void Initialize(IEntityObject entityObject, ISpatialCommunicator spatialCommunicator) + { + if (spatialCommunicator == null) + { + throw new ArgumentNullException("spatialCommunicator"); + } + + Entity = entityObject; + InitializeComponents(entityObject, spatialCommunicator); + } + + private void InitializeComponents(IEntityObject entityObject, ISpatialCommunicator spatialCommunicator) + { + // N.B. this one works with interfaces, GetComponents<> doesn't. + var spatialOsComponents = GetComponents(typeof(ISpatialOsComponentInternal)); + for (var i = 0; i < spatialOsComponents.Length; i++) + { + var spatialOsComponent = spatialOsComponents[i] as ISpatialOsComponentInternal; + if (spatialOsComponent == null) + { + continue; + } + + spatialOsComponent.Init(spatialCommunicator, entityObject); + } + } + + public void Clear() + { + GameObjectExtensions.RemoveFromLookupCache(Entity); + Entity = null; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObjectStorage.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObjectStorage.cs.meta new file mode 100644 index 0000000..438a0ed --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityObjectStorage.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c701eeccf5dd6384c9f3182e15c44780 +timeCreated: 1444833361 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityVisualizers.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityVisualizers.cs new file mode 100644 index 0000000..373fdcd --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityVisualizers.cs @@ -0,0 +1,423 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Visualizer; +using Improbable.Util.Injection; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// Manages the enabling and disabling of visualizers + /// + class EntityVisualizers : IEntityVisualizers, IDisposable + { + private class DefaultMonobehaviourActivator : IMonobehaviourActivator + { + public void Enable(MonoBehaviour monoBehaviour) + { + monoBehaviour.enabled = true; + } + + public void Disable(MonoBehaviour monoBehaviour) + { + monoBehaviour.enabled = false; + } + } + + private static readonly DefaultMonobehaviourActivator DefaultActivator = new DefaultMonobehaviourActivator(); + + private readonly HashSet disabledVisualizers = new HashSet(); + private readonly HashSet requiredComponents = new HashSet(); + private bool requiredComponentsUpToDate; + private bool disposing; + private readonly HashSet authoritativeComponents = new HashSet(); + private readonly IList invalidatorsList; + + private IMonobehaviourActivator activator = DefaultActivator; + + /// + /// Create a new instance of EntityVisualizers. + /// + /// The GameObject associated with the entity. + internal EntityVisualizers(GameObject underlyingGameObject) + { + if (underlyingGameObject == null) + { + throw new ArgumentNullException("underlyingGameObject"); + } + + ExtractedVisualizers = VisualizerMetadataLookup.ExtractVisualizers(underlyingGameObject); + + + OnUserException = Debug.LogException; + invalidatorsList = new List(); + Initialize(); + } + + /// + public IList ExtractedVisualizers { get; private set; } + + /// + /// For testing use only, i.e. this method should not be called by user code. + /// + /// A copy of the required components. + internal HashSet GetCopyOfRequiredComponents() + { + return new HashSet(requiredComponents); + } + + /// + public HashSet RequiredComponents + { + get + { + if (!requiredComponentsUpToDate) + { + CalculateRequiredComponents(); + } + + return requiredComponents; + } + } + + /// + public Action OnUserException { get; set; } + + /// + /// Registers to be used intead + /// of the simple default implementation. + /// + internal void RegisterActivationController(IMonobehaviourActivator activatorToUse) + { + this.activator = activatorToUse; + } + + /// + /// Disposing of entity visualizers is a subtle process. First, the visualisers are deactivated, + /// which can run user code --- this user code must run in an environment where everything + /// generally still works as expected; the exception is that calls to public methods of this + /// class will be ignored, because they could interfere with the disposal. Then, all event + /// handlers are removed; this should not trigger any events in itself. Only then, it is safe + /// to null visualizer fields. + /// + public void Dispose() + { + disposing = true; + DeactivateVisualizers(); + NullVisualizerFields(); + invalidatorsList.Clear(); + disposing = false; + } + + /// + public void TryEnableVisualizers(IList visualizersToEnable) + { + if (disposing) + { + return; + } + + var enabledVisualizersCount = 0; + for (var i = 0; i < visualizersToEnable.Count; i++) + { + var visualizer = visualizersToEnable[i]; + if (IsMarkedAsDisabled(visualizer)) + { + enabledVisualizersCount++; + EnableVisualizer(visualizer); + } + else + { + Debug.LogWarningFormat("Visualiser {0} was not previously disabled, cannot enable.", visualizer.GetType().Name); + } + } + + if (enabledVisualizersCount > 0) + { + TriggerRequiredComponentsPotentiallyChanged(); + } + } + + /// + public void DisableVisualizers(IList visualizersToDisable) + { + if (disposing) + { + return; + } + + var disabledVisualizersCount = 0; + for (var i = 0; i < visualizersToDisable.Count; i++) + { + var visualizer = visualizersToDisable[i]; + if (!IsMarkedAsDisabled(visualizer)) + { + disabledVisualizersCount++; + DisableVisualizer(visualizer); + } + } + + if (disabledVisualizersCount > 0) + { + TriggerRequiredComponentsPotentiallyChanged(); + } + } + + private void DeactivateVisualizers() + { + for (var i = 0; i < ExtractedVisualizers.Count; i++) + { + Deactivate(ExtractedVisualizers[i]); + } + } + + private void NullVisualizerFields() + { + for (var i = 0; i < ExtractedVisualizers.Count; i++) + { + NullAllFields(ExtractedVisualizers[i]); + } + } + + private void Activate(MonoBehaviour visualizer) + { + try + { + activator.Enable(visualizer); + } + catch (Exception ex) + { + if (OnUserException != null) + { + OnUserException(ex, visualizer); + } + } + } + + private void Deactivate(MonoBehaviour visualizer) + { + try + { + activator.Disable(visualizer); + } + catch (Exception ex) + { + if (OnUserException != null) + { + OnUserException(ex, visualizer); + } + } + } + + /// + /// For internal use only, i.e. this method should not be called by user code. + /// + internal void OnComponentAdded(IComponentMetaclass componentMetaclass, object component) + { + for (var i = 0; i < ExtractedVisualizers.Count; i++) + { + InjectReaders(ExtractedVisualizers[i], component); + } + + TriggerRequiredComponentsPotentiallyChanged(); + } + + /// + /// For internal use only, i.e. this method should not be called by user code. + /// + internal void OnComponentRemoved(IComponentMetaclass componentMetaclass, object component) + { + for (var i = 0; i < ExtractedVisualizers.Count; i++) + { + InjectNullAndDisable(ExtractedVisualizers[i], componentMetaclass, component.GetType()); + } + + TriggerRequiredComponentsPotentiallyChanged(); + } + + /// + /// For internal use only, i.e. this method should not be called by user code. + /// + internal void OnAuthorityChanged(IComponentMetaclass componentMetaclass, Authority authority, object component) + { + for (var i = 0; i < ExtractedVisualizers.Count; i++) + { + var visualizer = ExtractedVisualizers[i]; + var field = VisualizerMetadataLookup.Instance.GetFieldInfo(component.GetType(), visualizer.GetType()); + if (field != null && VisualizerMetadataLookup.Instance.IsWriter(field)) + { + if (authority == Authority.Authoritative || authority == Authority.AuthorityLossImminent) + { + InjectField(visualizer, field, component); + authoritativeComponents.Add(componentMetaclass.ComponentId); + UpdateActivation(visualizer); + } + else + { + Deactivate(visualizer); + field.SetValue(visualizer, null); + authoritativeComponents.Remove(componentMetaclass.ComponentId); + } + } + } + + TriggerRequiredComponentsPotentiallyChanged(); + } + + private void DisableVisualizer(MonoBehaviour visualizer) + { + disabledVisualizers.Add(visualizer); + UpdateActivation(visualizer); + } + + private void EnableVisualizer(MonoBehaviour visualizer) + { + disabledVisualizers.Remove(visualizer); + UpdateActivation(visualizer); + } + + private void Initialize() + { + for (var i = 0; i < ExtractedVisualizers.Count; i++) + { + var visualizer = ExtractedVisualizers[i]; + + if (VisualizerMetadataLookup.Instance.DontEnableOnStart(visualizer.GetType())) + { + disabledVisualizers.Add(visualizer); + } + + UpdateActivation(visualizer); + } + + TriggerRequiredComponentsPotentiallyChanged(); + } + + private bool IsMarkedAsDisabled(MonoBehaviour visualizer) + { + return disabledVisualizers.Contains(visualizer); + } + + /// + public void AddInvalidator(IEntityInterestedComponentsInvalidator invalidator) + { + invalidatorsList.Add(invalidator); + } + + /// + public void RemoveInvalidator(IEntityInterestedComponentsInvalidator invalidator) + { + invalidatorsList.Remove(invalidator); + } + + private void TriggerRequiredComponentsPotentiallyChanged() + { + requiredComponentsUpToDate = false; + for (int i = 0; i < invalidatorsList.Count; i++) + { + invalidatorsList[i].OnInterestedComponentsPotentiallyChanged(); + } + } + + internal void CalculateRequiredComponents() + { + requiredComponents.Clear(); + for (var i = 0; i < ExtractedVisualizers.Count; i++) + { + var visualizer = ExtractedVisualizers[i]; + + if (!IsMarkedAsDisabled(visualizer) && AllFieldWritersInjected(visualizer)) + { + var visualizerType = visualizer.GetType(); + var requiredReaders = VisualizerMetadataLookup.Instance.GetRequiredReaderComponentIds(visualizerType); + // NOTE: Using indexed for and Set.Add instead of UnionWith because UnionWith allocates memory (enumerators) + for (var componentIndex = 0; componentIndex < requiredReaders.Count; componentIndex++) + { + requiredComponents.Add(requiredReaders[componentIndex]); + } + } + } + + requiredComponentsUpToDate = true; + } + + private void NullAllFields(object visualizer) + { + var fields = VisualizerMetadataLookup.Instance.GetRequiredReadersWriters(visualizer.GetType()); + for (var index = 0; index < fields.Count; index++) + { + var fieldInfo = fields[index]; + fieldInfo.SetValue(visualizer, null); + } + } + + private void InjectReaders(MonoBehaviour visualizer, object component) + { + var field = VisualizerMetadataLookup.Instance.GetFieldInfo(component.GetType(), visualizer.GetType()); + if (field != null && !VisualizerMetadataLookup.Instance.IsWriter(field)) + { + InjectField(visualizer, field, component); + UpdateActivation(visualizer); + } + } + + private void InjectField(object visualizer, IMemberAdapter field, object component) + { + field.SetValue(visualizer, component); + } + + private void UpdateActivation(MonoBehaviour visualizer) + { + if (IsMarkedAsDisabled(visualizer)) + { + Deactivate(visualizer); + } + else if (VisualizerMetadataLookup.Instance.AreAllRequiredFieldsInjectable(visualizer.GetType()) + && AllFieldReadersAndWritersInjected(visualizer)) + { + Activate(visualizer); + } + } + + private bool AllFieldReadersAndWritersInjected(object visualizer) + { + var required = VisualizerMetadataLookup.Instance.GetRequiredReadersWriters(visualizer.GetType()); + return AllFieldsInjected(visualizer, required); + } + + private bool AllFieldWritersInjected(object visualizer) + { + var requiredWriters = VisualizerMetadataLookup.Instance.GetRequiredWriters(visualizer.GetType()); + return AllFieldsInjected(visualizer, requiredWriters); + } + + private static bool AllFieldsInjected(object visualizer, IList fields) + { + for (var index = 0; index < fields.Count; index++) + { + var fieldInfo = fields[index]; + if (fieldInfo.GetValue(visualizer) == null) + { + return false; + } + } + + return true; + } + + private void InjectNullAndDisable(MonoBehaviour visualizer, IComponentMetaclass componentMetaclass, Type componentType) + { + var field = VisualizerMetadataLookup.Instance.GetFieldInfo(componentType, visualizer.GetType()); + if (field == null) + { + return; + } + + Deactivate(visualizer); + field.SetValue(visualizer, null); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityVisualizers.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityVisualizers.cs.meta new file mode 100644 index 0000000..8245fb7 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/EntityVisualizers.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 66de2353b4d16914aa496c25c80af938 +timeCreated: 1517582615 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ICanAttachEditorDataObject.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ICanAttachEditorDataObject.cs new file mode 100644 index 0000000..68a764a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ICanAttachEditorDataObject.cs @@ -0,0 +1,20 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.CodeGeneration +{ + /// + /// Implementing class can accept attaching of Editor Data Object. + /// + public interface ICanAttachEditorDataObject + { + /// + /// Attaches the supplied editor data object. + /// + void AttachEditorDataObject(IComponentEditorDataObject editorDataObject); + + /// + /// Removes attached editor data object. + /// + void RemoveEditorDataObject(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ICanAttachEditorDataObject.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ICanAttachEditorDataObject.cs.meta new file mode 100644 index 0000000..c808f60 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ICanAttachEditorDataObject.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5c88b68498d252745bfd913dc37901cb +timeCreated: 1490370009 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentAdder.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentAdder.cs new file mode 100644 index 0000000..1edc851 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentAdder.cs @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Worker; + +namespace Improbable.Unity.Entity +{ + /// + /// An that has all of its required components set. + /// Call to add more components, or to complete the + /// . + /// + public interface IComponentAdder + { + /// + /// Adds a component (with the specified write ACL) to the entity. + /// + IComponentAdder AddComponent(IComponentData data, WorkerRequirementSet writeAcl) where C : IComponentMetaclass; + + /// + /// Sets the write authority on the ACL component. + /// + IComponentAdder SetEntityAclComponentWriteAccess(WorkerRequirementSet writeAcl); + + /// + /// Builds the . This method cannot be called more than once. + /// + Worker.Entity Build(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentAdder.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentAdder.cs.meta new file mode 100644 index 0000000..cd0809d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentAdder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5582d874a006b5245a50389e508da20a +timeCreated: 1498667559 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentEditorDataObject.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentEditorDataObject.cs new file mode 100644 index 0000000..f191219 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentEditorDataObject.cs @@ -0,0 +1,21 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.CodeGeneration +{ + /// + /// Bridges SpatialOS component classes with component editors in Unity Editor. + /// + interface IComponentEditorDataObject : IComponentEditorDataObject { } + + /// + /// Bridges SpatialOS component classes with component editors in Unity Editor. + /// + public interface IComponentEditorDataObject + { + void LogComponentUpdate(string componentName, object componentValue); + void LogCommandRequest(DateTime timestamp, string commandName, object payload); + void SendUpdateLog(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentEditorDataObject.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentEditorDataObject.cs.meta new file mode 100644 index 0000000..273fcf1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentEditorDataObject.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 60f297019f154b346a40022e8a17baa3 +timeCreated: 1490612600 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentInterestOverridesUpdateReceiver.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentInterestOverridesUpdateReceiver.cs new file mode 100644 index 0000000..4f0aadb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentInterestOverridesUpdateReceiver.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Worker; + +namespace Improbable.Unity.Entity +{ + /// + /// Can be registered to an IEntityInterestedComponentsReporter to listen to updates to component interest overrides. + /// + public interface IComponentInterestOverridesUpdateReceiver + { + /// + /// Callback to be invoked when an entity's component interest overrides have changed. + /// + void OnComponentInterestOverridesUpdated(EntityId entity, System.Collections.Generic.Dictionary interestOverrides); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentInterestOverridesUpdateReceiver.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentInterestOverridesUpdateReceiver.cs.meta new file mode 100644 index 0000000..7104fcd --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IComponentInterestOverridesUpdateReceiver.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f04e07d810f4b0147a0489d4e07728c8 +timeCreated: 1496307051 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponentInterestOverridesUpdater.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponentInterestOverridesUpdater.cs new file mode 100644 index 0000000..8990865 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponentInterestOverridesUpdater.cs @@ -0,0 +1,30 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// Calculates interested components of an entity. + /// + public interface IEntityComponentInterestOverridesUpdater + { + /// + /// Request recalculation of interested components for an entity. + /// + void InvalidateEntity(IEntityObject entityObject); + + /// + /// Cancels recalculation of interested components for an entity. + /// + void RemoveEntity(IEntityObject entityObject); + + /// + /// Add an IComponentInterestOverridesUpdateReceiver which listens to update events. + /// + void AddUpdateReceiver(IComponentInterestOverridesUpdateReceiver updateReceiver); + + /// + /// Remove an IComponentInterestOverridesUpdateReceiver and stop it from getting notified about update events. + /// + void RemoveUpdateReceiver(IComponentInterestOverridesUpdateReceiver updateReceiver); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponentInterestOverridesUpdater.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponentInterestOverridesUpdater.cs.meta new file mode 100644 index 0000000..67a040e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponentInterestOverridesUpdater.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b76a6e1414b3456479cbcf83c5a40cd5 +timeCreated: 1484563915 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponents.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponents.cs new file mode 100644 index 0000000..44fb525 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponents.cs @@ -0,0 +1,39 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using Improbable.Unity.CodeGeneration; + +namespace Improbable.Unity.Entity +{ + /// + /// Manages components attached to an entity. + /// + public interface IEntityComponents + { + /// + /// Returns a set of interested component ids. + /// + IDictionary RegisteredComponents { get; } + + /// + /// Add an interested component. + /// + void RegisterInterestedComponent(uint componentId, ISpatialOsComponentInternal component); + + /// + /// Removes an interested component. + /// + void DeregisterInterestedComponent(uint componentId); + + /// + /// Add an IEntityInterestedComponentsInvalidator which listens to potential changes in interested components. + /// + void AddInvalidator(IEntityInterestedComponentsInvalidator invalidator); + + /// + /// Remove an IEntityInterestedComponentsInvalidator and stop it from getting notified about potential changes in + /// interested components. + /// + void RemoveInvalidator(IEntityInterestedComponentsInvalidator invalidator); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponents.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponents.cs.meta new file mode 100644 index 0000000..e1c6481 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityComponents.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 54f9c7e63b8f45e44abdbdcf5fcee7d3 +timeCreated: 1484563915 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityInterestedComponentsInvalidator.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityInterestedComponentsInvalidator.cs new file mode 100644 index 0000000..283a2b8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityInterestedComponentsInvalidator.cs @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// Can be registered to an IEntityComponents or IEntityVisualizers to listen to potential changes in interested + /// components. + /// + public interface IEntityInterestedComponentsInvalidator + { + /// + /// Callback to be invoked when an entity's interested components have potentially changed. + /// + void OnInterestedComponentsPotentiallyChanged(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityInterestedComponentsInvalidator.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityInterestedComponentsInvalidator.cs.meta new file mode 100644 index 0000000..9ec84ef --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityInterestedComponentsInvalidator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 487ba736db7d3aa4fb5ae2629ae1e471 +timeCreated: 1492089381 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityObject.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityObject.cs new file mode 100644 index 0000000..a400db1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityObject.cs @@ -0,0 +1,37 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// Contains information about a SpatialOS entity and its associated GameObject. + /// + public interface IEntityObject : IPipelineEntityComponentOpsReceiver + { + /// + /// Returns the entity's entity id. + /// + EntityId EntityId { get; } + + /// + /// Returns the entity's prefab name. + /// + string PrefabName { get; } + + /// + /// Returns the GameObject associated with this entity. + /// + GameObject UnderlyingGameObject { get; } + + /// + /// Object for managing visualizers. + /// + IEntityVisualizers Visualizers { get; } + + /// + /// Object for managing components. + /// + IEntityComponents Components { get; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityObject.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityObject.cs.meta new file mode 100644 index 0000000..a4573d3 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityObject.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5c5abc91775f4f84fa3eb8d4406e2e44 +timeCreated: 1484564023 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityTemplateProvider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityTemplateProvider.cs new file mode 100644 index 0000000..299279b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityTemplateProvider.cs @@ -0,0 +1,40 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// An IEntityTemplateProvider can look up a GameObject to use as a template for a prefabName. + /// + public interface IEntityTemplateProvider + { + /// + /// PrepareTemplate is an asynchronous method guaranteed to be called at least once before the GameObject template + /// required for a particular prefabName is requested. + /// Implementors must call onSuccess once the IEntityTemplateProvider is ready to accept GetEntityTemplate calls, and + /// onError if it was unable to get ready. + /// + /// The prefabName of the entity. + /// the continuation to call if preparation for the entity asset was successful. + /// the continuation to call if preparation for the entity asset failed. + void PrepareTemplate(string prefabName, Action onSuccess, Action onError); + + /// + /// CancelAllTemplatePreparations cancels all active operations. + /// The onSuccess and onError callbacks for these operations should not be called. + /// + void CancelAllTemplatePreparations(); + + /// + /// GetEntityTemplate must return a template GameObject that will be instantiated to make new instances of entities + /// with the same prefabName. + /// Subsequent calls with the same prefabName should return the same GameObject. + /// PrepareTemplate will always have been called at least once with this prefabName. + /// + /// The prefab name of the entity. + /// A GameObject respresenting the prefab with the given prefabName. + GameObject GetEntityTemplate(string prefabName); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityTemplateProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityTemplateProvider.cs.meta new file mode 100644 index 0000000..6779bba --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityTemplateProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 52f62e34f3e11594dbf26547c6d4c4a8 +timeCreated: 1484562251 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityVisualizers.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityVisualizers.cs new file mode 100644 index 0000000..bce50f8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityVisualizers.cs @@ -0,0 +1,53 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// Manages the enabling and disabling of visualizers + /// + public interface IEntityVisualizers + { + /// + /// Returns a list of MonoBehaviours that use RequireAttribute to acquire Component readers and writers. + /// + IList ExtractedVisualizers { get; } + + /// + /// Called when an exception is caught during the process of enabling or disabling a MonoBehaviour. + /// + /// + /// Defaults to Debug.LogException. + /// + Action OnUserException { get; set; } + + /// + /// Returns a set of required component ids. + /// + HashSet RequiredComponents { get; } + + /// + /// Add an IEntityInterestedComponentsInvalidator which listens to potential changes in interested components. + /// + void AddInvalidator(IEntityInterestedComponentsInvalidator invalidator); + + /// + /// Remove an IEntityInterestedComponentsInvalidator and stop it from getting notified about potential changes in + /// interested components. + /// + void RemoveInvalidator(IEntityInterestedComponentsInvalidator invalidator); + + /// + /// Manually disable visualizers. + /// + void DisableVisualizers(IList visualizersToDisable); + + /// + /// Tries to manually enable visualizers. + /// + void TryEnableVisualizers(IList visualizersToEnable); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityVisualizers.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityVisualizers.cs.meta new file mode 100644 index 0000000..823fded --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IEntityVisualizers.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9cf22f5848166124181a5cf52f74d574 +timeCreated: 1484564023 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IInterestedComponentUpdaterProvider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IInterestedComponentUpdaterProvider.cs new file mode 100644 index 0000000..b615579 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IInterestedComponentUpdaterProvider.cs @@ -0,0 +1,15 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// Interface for classes able to provide IEntityComponentInterestOverridesUpdater objects. + /// + public interface IInterestedComponentUpdaterProvider + { + /// + /// Return an IEntityComponentInterestOverridesUpdater. + /// + IEntityComponentInterestOverridesUpdater GetEntityInterestedComponentsUpdater(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IInterestedComponentUpdaterProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IInterestedComponentUpdaterProvider.cs.meta new file mode 100644 index 0000000..9909bd8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IInterestedComponentUpdaterProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 845c17318dd56844aafc8885ec1c3bdc +timeCreated: 1492089382 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ILocalEntities.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ILocalEntities.cs new file mode 100644 index 0000000..a9790f3 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ILocalEntities.cs @@ -0,0 +1,9 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// Provides a view of all entities that exist on this worker. + /// + public interface ILocalEntities : IUniverse { } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ILocalEntities.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ILocalEntities.cs.meta new file mode 100644 index 0000000..396bc31 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/ILocalEntities.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c49ef34fa4102e34d8051c029aafe6d9 +timeCreated: 1495104853 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMetadataAdder.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMetadataAdder.cs new file mode 100644 index 0000000..0cc1a14 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMetadataAdder.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// An that needs to have a metadata component added with + /// . + /// + public interface IMetadataAdder + { + /// + /// Adds the required component. The next step is to call + /// . + /// + IPersistenceSetter AddMetadataComponent(string entityType); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMetadataAdder.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMetadataAdder.cs.meta new file mode 100644 index 0000000..bfd86b8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMetadataAdder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9167c05b43fc1a44f82349d21c66ce2b +timeCreated: 1498667559 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMonobehaviourActivator.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMonobehaviourActivator.cs new file mode 100644 index 0000000..044135a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMonobehaviourActivator.cs @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Unity +{ + /// + /// Implementing class can enable or disable MonoBehaviours. + /// + /// + /// Used in . + /// + interface IMonobehaviourActivator + { + /// + /// Enable MonoBehaviour. + /// + void Enable(MonoBehaviour monoBehaviour); + + /// + /// Disable MonoBehaviour. + /// + void Disable(MonoBehaviour monoBehaviour); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMonobehaviourActivator.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMonobehaviourActivator.cs.meta new file mode 100644 index 0000000..5985d92 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMonobehaviourActivator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ffb03be87f509b84188ddbe03fa5e4bb +timeCreated: 1495190630 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableLocalEntities.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableLocalEntities.cs new file mode 100644 index 0000000..81db899 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableLocalEntities.cs @@ -0,0 +1,9 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// Interface for interacting with the instances. + /// + interface IMutableLocalEntities : IMutableUniverse, ILocalEntities { } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableLocalEntities.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableLocalEntities.cs.meta new file mode 100644 index 0000000..7d6d1a4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableLocalEntities.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 3f8ab6e76d56d134185f1411f0026770 +timeCreated: 1495104852 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableUniverse.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableUniverse.cs new file mode 100644 index 0000000..1d0258b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableUniverse.cs @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// Interface for interacting with the instances. + /// + interface IMutableUniverse : IUniverse + { + /// + /// Adds the object to the universe with the given EntityId. + /// + void AddEntity(IEntityObject entity); + + /// + /// Removes the object with the given EntityId from the universe. + /// + /// + void Remove(EntityId entityId); + + /// + /// Removes all objects from the universe. + /// + void Clear(); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableUniverse.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableUniverse.cs.meta new file mode 100644 index 0000000..005f727 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IMutableUniverse.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1e496562f5aba664b954a28990150096 +timeCreated: 1494518600 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPersistenceSetter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPersistenceSetter.cs new file mode 100644 index 0000000..dc60b0c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPersistenceSetter.cs @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// An that needs to have its persistence value set with . + /// + public interface IPersistenceSetter + { + /// + /// If true, adds the component to the entity. The next step is to call + /// . + /// + IReadAclSetter SetPersistence(bool persistence); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPersistenceSetter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPersistenceSetter.cs.meta new file mode 100644 index 0000000..fbe98eb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPersistenceSetter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5fa44dbcc911b9d4dacf189f17ceac51 +timeCreated: 1498667559 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPipelineEntityComponentOpsReceiver.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPipelineEntityComponentOpsReceiver.cs new file mode 100644 index 0000000..3ead99f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPipelineEntityComponentOpsReceiver.cs @@ -0,0 +1,32 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Core; + +namespace Improbable.Unity.Entity +{ + /// + /// Provides an interface for accepting component related entity pipeline ops. + /// + public interface IPipelineEntityComponentOpsReceiver + { + /// + /// Endpoint for incoming AddComponent pipeline ops. + /// + void OnAddComponentPipelineOp(AddComponentPipelineOp op); + + /// + /// Endpoint for incoming RemoveComponent pipeline ops. + /// + void OnRemoveComponentPipelineOp(RemoveComponentPipelineOp op); + + /// + /// Endpoint for incoming UpdateComponent pipeline ops. + /// + void OnComponentUpdatePipelineOp(UpdateComponentPipelineOp op); + + /// + /// Endpoint for incoming ChangeAuthority pipeline ops. + /// + void OnAuthorityChangePipelineOp(ChangeAuthorityPipelineOp op); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPipelineEntityComponentOpsReceiver.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPipelineEntityComponentOpsReceiver.cs.meta new file mode 100644 index 0000000..764138b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPipelineEntityComponentOpsReceiver.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 75ce94b716a967f4d8e96ec5124dfd32 +timeCreated: 1497522661 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPositionAdder.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPositionAdder.cs new file mode 100644 index 0000000..703d2e5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPositionAdder.cs @@ -0,0 +1,32 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// An that needs to have a component added with + /// . + /// Multiple calls to will overwrite the previous value. + /// + public interface IPositionAdder + { + /// + /// Adds the required component with the specified write ACL. The next step is to call + /// . + /// + IMetadataAdder AddPositionComponent(Vector3 position, WorkerRequirementSet writeAcl); + + /// + /// Adds the required component with the specified write ACL. The next step is to call + /// . + /// + IMetadataAdder AddPositionComponent(Vector3d position, WorkerRequirementSet writeAcl); + + /// + /// Adds the required component with the specified write ACL. The next step is to call + /// . + /// + IMetadataAdder AddPositionComponent(Coordinates position, WorkerRequirementSet writeAcl); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPositionAdder.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPositionAdder.cs.meta new file mode 100644 index 0000000..507546c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPositionAdder.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: cf7fda100708e75448f8a540f362c0d4 +timeCreated: 1498667559 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPrefabFactory.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPrefabFactory.cs new file mode 100644 index 0000000..e7951f5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPrefabFactory.cs @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// Provides methods to create and destroy entities. + /// + /// + public interface IPrefabFactory + { + /// + /// Instantiates a GameObject from the given prefab. + /// + /// The prefab to instantiate. + /// The prefab name. + /// A new instance of the given prefabGameObject. + T MakeComponent(T prefabGameObject, string prefabName); + + /// + /// Destroys an existing GameObject. + /// + /// The object that is in the game. + /// The prefab name of the prefab that the object was instantiated from. + void DespawnComponent(T gameObject, string prefabName); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPrefabFactory.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPrefabFactory.cs.meta new file mode 100644 index 0000000..79c9a43 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IPrefabFactory.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 7043c0ceb4ee6164ebe652ab11010956 +timeCreated: 1484562251 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IReadAclSetter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IReadAclSetter.cs new file mode 100644 index 0000000..2f89471 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IReadAclSetter.cs @@ -0,0 +1,15 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + /// + /// An that needs to have its read ACL set with . + /// + public interface IReadAclSetter + { + /// + /// Sets the required read ACL. After this step, the entity has all of its required components. + /// + IComponentAdder SetReadAcl(WorkerRequirementSet readAcl); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IReadAclSetter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IReadAclSetter.cs.meta new file mode 100644 index 0000000..0f00f61 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IReadAclSetter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b1db4fd37fed4b546af51f63acd26171 +timeCreated: 1498667559 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IUniverse.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IUniverse.cs new file mode 100644 index 0000000..0a8c721 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IUniverse.cs @@ -0,0 +1,34 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; + +namespace Improbable.Unity.Entity +{ + /// + /// Contains all of the entities that currently exist on this worker. + /// + public interface IUniverse + { + /// + /// Checks if the entityId currently exists on this worker. + /// + bool ContainsEntity(EntityId entityId); + + /// + /// Get the EntityObject associated with entityId, or null if it doesn't exist. + /// + IEntityObject Get(EntityId entityId); + + /// + /// Gets all available EntityObjects for this worker. + /// + IEnumerable GetAll(); + + /// + /// Applies the supplied action to every member of the Universe. + /// + [Obsolete("Obsolete in 10.4.0. Migrate to use DefaultEntityPipeline and use custom entity tracking.")] + void IterateOverAllEntityObjects(Action action); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IUniverse.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IUniverse.cs.meta new file mode 100644 index 0000000..fab8849 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/IUniverse.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4d58f66846685c749a2ac26849871cfa +timeCreated: 1484562251 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides.meta new file mode 100644 index 0000000..9ade334 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 738205f84defbf746b6a5ba399e555e1 +folderAsset: yes +timeCreated: 1510916126 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/IWorkerComponentInterestOverrider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/IWorkerComponentInterestOverrider.cs new file mode 100644 index 0000000..807c31b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/IWorkerComponentInterestOverrider.cs @@ -0,0 +1,43 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using Improbable.Worker; + +namespace Improbable.Unity.Entity +{ + // #SNIPPET_START interface + /// + /// Provides the ability to override calculated component interests for the current worker. + /// These interests are calculated for a GameObject associated with a SpatialOS Entity based on: + /// a) The presence of the [Require] attribute on a field referencing a SpatialOS component on a MonoBehaviour. + /// b) The presence of a generated SpatialOS MonoBehaviour component. + /// + public interface IWorkerComponentInterestOverrider + { + /// + /// Sets an interest override for a component on ALL entities on the current worker. + /// + /// The component type. + /// The interest override to apply. + void SetComponentInterest(WorkerComponentInterest interest) where T : IComponentMetaclass; + + /// + /// Sets an interest override for a component on a specific entity on the current worker. + /// + /// The component type. + /// The entity that the override applies to. + /// The interest override to apply. + void SetComponentInterest(EntityId entityId, WorkerComponentInterest interest) where T : IComponentMetaclass; + + /// + /// Returns the set of component interest overrides active on the current worker for a specific entity, including any + /// global + /// overrides that have been set. Global overrides are themselves overridden by entity-specific overrides. + /// + /// The entity to retrieve. + /// A mapping of componentId to its override status. + IEnumerator> GetComponentInterest(EntityId entityId); + } + + // #SNIPPET_END interface +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/IWorkerComponentInterestOverrider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/IWorkerComponentInterestOverrider.cs.meta new file mode 100644 index 0000000..e63a207 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/IWorkerComponentInterestOverrider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 843d09ac159ba1342bcb68955eeed038 +timeCreated: 1510766978 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterest.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterest.cs new file mode 100644 index 0000000..075e2d2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterest.cs @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Entity +{ + // #SNIPPET_START enum + /// + /// Enumeration of modifications that can be made to the calculated component interest. + /// + /// See for more information about component interest. + public enum WorkerComponentInterest + { + /// + /// No override - use the calculated interest. + /// + Default, + + /// + /// Always be interested in a component, even if it hasn't been calculated. + /// + Always, + + /// + /// Never be interested in a component, even if it's been calculated. + /// + Never + } + + // #SNIPPET_END enum +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterest.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterest.cs.meta new file mode 100644 index 0000000..e42fd64 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterest.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0d556e3a5e1958447885cb1bc972d78c +timeCreated: 1510848938 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterestOverriderImpl.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterestOverriderImpl.cs new file mode 100644 index 0000000..3cdaf33 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterestOverriderImpl.cs @@ -0,0 +1,150 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Worker; + +namespace Improbable.Unity.Entity +{ + /// + internal class WorkerComponentInterestOverriderImpl : IWorkerComponentInterestOverrider + { + private readonly Dictionary globalInterests; + private readonly Dictionary> perEntityInterests; + + public WorkerComponentInterestOverriderImpl() + { + globalInterests = new Dictionary(); + perEntityInterests = new Dictionary>(); + } + + /// + /// Gets or sets the target to be notified when interest has changed. + /// + public Action InterestInvalidationHandler { get; set; } + + /// + /// Gets or sets the source of all EntityObjects that exist on the current worker. + /// + public ILocalEntities EntityObjects { get; set; } + + public void SetComponentInterest(WorkerComponentInterest interest) where T : IComponentMetaclass + { + var shouldNotify = ApplyInterestChanges(globalInterests, interest); + + if (InterestInvalidationHandler == null || EntityObjects == null || !shouldNotify) + { + return; + } + + foreach (var obj in EntityObjects.GetAll()) + { + // Only notify that a global interest has changed if it's not overridden on the entity. + if (!perEntityInterests.ContainsKey(obj.EntityId)) + { + InterestInvalidationHandler(obj); + } + } + } + + public void SetComponentInterest(EntityId entityId, WorkerComponentInterest interest) where T : IComponentMetaclass + { + var shouldNotify = true; + var componentId = Dynamic.GetComponentId(); + if (perEntityInterests.ContainsKey(entityId)) + { + // We've already setup interests, so modify what's there. + var workerComponentInterests = perEntityInterests[entityId]; + shouldNotify = ApplyInterestChanges(workerComponentInterests, interest); + + // Clean up if no overrides remain for the entity. + if (workerComponentInterests.Count == 0) + { + perEntityInterests.Remove(entityId); + } + } + else if (interest == WorkerComponentInterest.Default) + { + // No previous interest to remove/update, nothing has changed. + shouldNotify = false; + } + else + { + // Create the first interest override + perEntityInterests[entityId] = new Dictionary { { componentId, interest } }; + } + + if (!shouldNotify || InterestInvalidationHandler == null) + { + return; + } + + var obj = EntityObjects.Get(entityId); + if (obj != null) + { + InterestInvalidationHandler(obj); + } + } + + public IEnumerator> GetComponentInterest(EntityId entityId) + { + // This function "merges" the global interest overrides collection with a per-entity interest overrides collection (if present.) + + Dictionary specificInterests; + perEntityInterests.TryGetValue(entityId, out specificInterests); + + // First, return all non-overridden global interests. + foreach (var kv in globalInterests) + { + // Only return a global override if it's not been overridden later by an entity-specific override. + if (specificInterests == null || !specificInterests.ContainsKey(kv.Key)) + { + yield return new KeyValuePair(kv.Key, kv.Value); + } + } + + // Then, move on to entity-specific interests, if any. + if (specificInterests == null) + { + yield break; + } + + foreach (var interest in specificInterests) + { + yield return interest; + } + } + + private static bool ApplyInterestChanges(IDictionary dict, WorkerComponentInterest interest) where T : IComponentMetaclass + { + var shouldNotify = true; + + var componentId = Dynamic.GetComponentId(); + switch (interest) + { + case WorkerComponentInterest.Default: + // Only notify if interest was previously overridden. + shouldNotify = dict.Remove(componentId); + break; + case WorkerComponentInterest.Always: + case WorkerComponentInterest.Never: + WorkerComponentInterest oldInterest; + if (dict.TryGetValue(componentId, out oldInterest) && oldInterest == interest) + { + // The value of the interest isn't changing. + shouldNotify = false; + } + else + { + dict[componentId] = interest; + } + + break; + default: + throw new ArgumentOutOfRangeException("interest", interest, null); + } + + return shouldNotify; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterestOverriderImpl.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterestOverriderImpl.cs.meta new file mode 100644 index 0000000..0c0b654 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/InterestOverrides/WorkerComponentInterestOverriderImpl.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8930ffc1666e9ad41a55e577c639e741 +timeCreated: 1510916131 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/LocalEntities.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/LocalEntities.cs new file mode 100644 index 0000000..3b41ae8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/LocalEntities.cs @@ -0,0 +1,93 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Unity.Entity +{ + /// + /// + /// Contains all of the entities that currently exist on this worker. + /// + public class LocalEntities : IMutableLocalEntities + { + private readonly EntityObjectDictionary entities = new EntityObjectDictionary(); + + private static readonly LocalEntities LocalEntitiesInstance = new LocalEntities(); + + public IEnumerable GetAll() + { + return entities.Values; + } + + /// + /// Singleton instance of . + /// + public static ILocalEntities Instance + { + get { return LocalEntitiesInstance; } + } + + /// + /// Mutable interface to . + /// + /// + /// Should only be accessed from code related to entity lifecycle. + /// + internal static IMutableLocalEntities Internal + { + get { return LocalEntitiesInstance; } + } + + public void IterateOverAllEntityObjects(Action action) + { + foreach (var entity in entities.Values) + { + action(entity.EntityId, entity); + } + } + + public IEntityObject Get(EntityId entityId) + { + IEntityObject entity; + entities.TryGetValue(entityId, out entity); + return entity; + } + + public bool ContainsEntity(EntityId entityId) + { + return entities.Contains(entityId); + } + + public void AddEntity(IEntityObject entity) + { + if (entity == null) + { + Debug.LogError("Storing a null EntityObject"); + return; + } + + if (entities.Contains(entity)) + { + Debug.LogErrorFormat("Trying to store a duplicate with EntityId {0} for object {1}", entity.EntityId, entity.UnderlyingGameObject.name); + return; + } + + entities.Add(entity); + } + + public void Remove(EntityId entityId) + { + if (!entities.Remove(entityId)) + { + Debug.LogErrorFormat("Trying to remove an unknown EntityId {0}", entityId); + } + } + + public void Clear() + { + entities.Clear(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/LocalEntities.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/LocalEntities.cs.meta new file mode 100644 index 0000000..017d551 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Entity/LocalEntities.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 89fe08ea35ce07c479894ebd64f909bb +timeCreated: 1444833360 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline.meta new file mode 100644 index 0000000..e45e079 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 790d5c71b4388c5468e4384ec78e1a46 +folderAsset: yes +timeCreated: 1495101829 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/DefaultEntityPipeline.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/DefaultEntityPipeline.meta new file mode 100644 index 0000000..ef8d40d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/DefaultEntityPipeline.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b6f768ede7b53e2459a160b69ae8566f +folderAsset: yes +timeCreated: 1495101829 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/DefaultEntityPipeline/README.md b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/DefaultEntityPipeline/README.md new file mode 100644 index 0000000..02dad32 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/DefaultEntityPipeline/README.md @@ -0,0 +1,3 @@ +# Default Entity Pipeline + +* The reference implementation of the new entity spawning pipeline will be placed here * \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/DefaultEntityPipeline/README.md.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/DefaultEntityPipeline/README.md.meta new file mode 100644 index 0000000..78199f2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/DefaultEntityPipeline/README.md.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3c5b359577676c346aeb9b76fd65204f +timeCreated: 1495104847 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EmptyEntityBlock.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EmptyEntityBlock.cs new file mode 100644 index 0000000..09385bb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EmptyEntityBlock.cs @@ -0,0 +1,39 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// Empty entity block, added at the end of the pipeline to + /// make sure that + /// does not need to be checked for null in all block implementations. + /// + sealed class EmptyEntityBlock : IEntityPipelineBlock + { + /// + public void AddEntity(AddEntityPipelineOp addEntity) { } + + /// + public void RemoveEntity(RemoveEntityPipelineOp removeEntityOp) { } + + /// + public void CriticalSection(CriticalSectionPipelineOp criticalSectionOp) { } + + /// + public void AddComponent(AddComponentPipelineOp addComponentOp) { } + + /// + public void RemoveComponent(RemoveComponentPipelineOp removeComponentOp) { } + + /// + public void ChangeAuthority(ChangeAuthorityPipelineOp changeAuthorityOp) { } + + /// + public void UpdateComponent(UpdateComponentPipelineOp updateComponentOp) { } + + /// + public void ProcessOps() { } + + /// + public IEntityPipelineBlock NextEntityBlock { get; set; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EmptyEntityBlock.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EmptyEntityBlock.cs.meta new file mode 100644 index 0000000..1a2d95d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EmptyEntityBlock.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fb32e9d0c3b106042a3849a863d41461 +timeCreated: 1493728916 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipeline.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipeline.cs new file mode 100644 index 0000000..d02547f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipeline.cs @@ -0,0 +1,225 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Entity.Component; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Implementation of the Entity Pipeline. + /// + public class EntityPipeline : IEntityPipelineInternal + { + private static readonly EntityPipeline PipelineInstance = new EntityPipeline(); + + internal static IEntityPipelineInternal Internal + { + get { return PipelineInstance; } + } + + public static IEntityPipeline Instance + { + get { return PipelineInstance; } + } + + private static readonly EmptyEntityBlock FinalBlock = new EmptyEntityBlock(); + + private IEntityPipelineBlock firstBlock; + private IEntityPipelineBlock lastBlock; + + private bool started = false; + + private ISpatialCommunicator spatialCommunicator; + private Connection connection; + private Dispatcher dispatcher; + private IList registeredDispatcherCallbacks = new List(); + + public IEntityPipeline AddBlock(IEntityPipelineBlock block) + { + if (started) + { + throw new InvalidOperationException("Cannot add blocks after the pipeline has been started."); + } + + if (firstBlock == null) + { + firstBlock = block; + } + + if (lastBlock != null) + { + lastBlock.NextEntityBlock = block; + } + + lastBlock = block; + return this; + } + + /// + public void Start(ISpatialCommunicator spatialCommunicator) + { + if (started) + { + throw new InvalidOperationException("The pipeline has already been started."); + } + + this.spatialCommunicator = spatialCommunicator; + RegisterDispatcherCallbacks(); + + // Set the empty block as the last block to prevent NREs being thrown + // when the last block uses 'NextEntityBlock' property. + lastBlock.NextEntityBlock = FinalBlock; + + started = true; + } + + private void RegisterDispatcherCallbacks() + { + registeredDispatcherCallbacks.Add(spatialCommunicator.RegisterAddEntity(addEntityOp => firstBlock.AddEntity(new AddEntityPipelineOp { DispatcherOp = addEntityOp }))); + registeredDispatcherCallbacks.Add(spatialCommunicator.RegisterRemoveEntity(removeEntityOp => firstBlock.RemoveEntity(new RemoveEntityPipelineOp { DispatcherOp = removeEntityOp }))); + registeredDispatcherCallbacks.Add(spatialCommunicator.RegisterCriticalSection(criticalSectionOp => firstBlock.CriticalSection(new CriticalSectionPipelineOp { DispatcherOp = criticalSectionOp }))); + } + + private void UnRegisterDispatcherCallbacks() + { + if (spatialCommunicator == null) + { + return; + } + + for (int i = 0; i < registeredDispatcherCallbacks.Count; i++) + { + spatialCommunicator.Remove(registeredDispatcherCallbacks[i]); + } + + registeredDispatcherCallbacks.Clear(); + } + + /// + public void ProcessOps() + { + var block = firstBlock; + while (block != null) + { + try + { + block.ProcessOps(); + } + catch (Exception e) + { + Debug.LogErrorFormat("Exception was thrown while processing ops in block {0}.", block.GetType().Name); + Debug.LogException(e); + } + + block = block.NextEntityBlock; + } + } + + private class RegisterHandler : Dynamic.Handler + { + EntityPipeline impl; + + public RegisterHandler(EntityPipeline impl) + { + this.impl = impl; + } + + public void Accept(C metaclass) where C : IComponentMetaclass + { + var factory = metaclass as IComponentFactory; + if (factory != null) + { + factory.RegisterWithConnection(impl.connection, impl.dispatcher, + impl.firstBlock.ToComponentFactoryCallbacks()); + } + else + { + Debug.LogErrorFormat("Could not register {0} as a {1}", metaclass, typeof(IComponentFactory)); + } + } + } + + private class UnregisterHandler : Dynamic.Handler + { + EntityPipeline impl; + + public UnregisterHandler(EntityPipeline impl) + { + this.impl = impl; + } + + public void Accept(C metaclass) where C : IComponentMetaclass + { + var factory = metaclass as IComponentFactory; + if (factory != null) + { + factory.UnregisterWithConnection(impl.connection, impl.dispatcher); + } + else + { + Debug.LogErrorFormat("Could not unregister {0} as a {1}", metaclass, typeof(IComponentFactory)); + } + } + } + + /// + public void RegisterComponentFactories(Connection connection, Dispatcher dispatcher) + { + this.connection = connection; + this.dispatcher = dispatcher; + + if (started) + { + throw new InvalidOperationException("Cannot register component factories after the pipeline has been started."); + } + + Dynamic.ForEachComponent(new RegisterHandler(this)); + } + + /// + public void UnregisterComponentFactories() + { + if (connection == null || dispatcher == null) + { + return; + } + + Dynamic.ForEachComponent(new UnregisterHandler(this)); + } + + public void Dispose() + { + UnRegisterDispatcherCallbacks(); + UnregisterComponentFactories(); + + var block = firstBlock; + while (block != null) + { + var disposable = block as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + + block = block.NextEntityBlock; + } + + firstBlock = null; + lastBlock = null; + started = false; + + spatialCommunicator = null; + connection = null; + dispatcher = null; + } + + /// + public bool IsEmpty + { + get { return firstBlock == null; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipeline.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipeline.cs.meta new file mode 100644 index 0000000..1f6f5fc --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipeline.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9346e6b4df8920e479357d988adf2187 +timeCreated: 1491397126 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineExtensions.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineExtensions.cs new file mode 100644 index 0000000..9c5f55a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineExtensions.cs @@ -0,0 +1,103 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Entity.Component; +using Improbable.Worker; + +namespace Improbable.Unity.Core +{ + /// + /// A collection of utility extension methods to use with objects. + /// + public static class EntityPipelineExtensions + { + /// + /// Class for wrapping the (EntityId, IComponentMetaclass, object) + /// method argument sets into a discrete ops. + /// + private class FactoryCallbackWrapper + { + private readonly IEntityPipelineBlock block; + + internal FactoryCallbackWrapper(IEntityPipelineBlock block) + { + this.block = block; + } + + internal void AddComponent(EntityId entityId, IComponentMetaclass componentMetaclass, object component) + { + block.AddComponent(new AddComponentPipelineOp { EntityId = entityId, ComponentMetaClass = componentMetaclass, ComponentObject = component }); + } + + internal void RemoveComponent(EntityId entityId, IComponentMetaclass componentMetaclass, object component) + { + block.RemoveComponent(new RemoveComponentPipelineOp { EntityId = entityId, ComponentMetaClass = componentMetaclass, ComponentObject = component }); + } + + internal void ChangeAuthority(EntityId entityId, IComponentMetaclass componentMetaclass, Authority authority, object component) + { + block.ChangeAuthority(new ChangeAuthorityPipelineOp { EntityId = entityId, ComponentMetaClass = componentMetaclass, ComponentObject = component, Authority = authority }); + } + + internal void UpdateComponent(EntityId entityId, IComponentMetaclass componentMetaclass, object update) + { + block.UpdateComponent(new UpdateComponentPipelineOp { EntityId = entityId, ComponentMetaClass = componentMetaclass, UpdateObject = update }); + } + } + + /// + /// Registers the given as + /// objects used in base SDK instances in generated code. + /// + /// + /// + public static ComponentFactoryCallbacks ToComponentFactoryCallbacks(this IEntityPipelineBlock block) + { + var wrapper = new FactoryCallbackWrapper(block); + return new ComponentFactoryCallbacks + { + OnComponentAdded = wrapper.AddComponent, + OnComponentRemoved = wrapper.RemoveComponent, + OnAuthorityChanged = wrapper.ChangeAuthority, + OnComponentUpdated = wrapper.UpdateComponent + }; + } + + /// + /// Convenience method for dispatching the supplied op to the + /// appropriate method in the entity pipeline block. + /// + /// + /// Thrown when op object is of an unrecognised type. + /// + public static void DispatchOp(this IEntityPipelineBlock block, IEntityPipelineOp pipelineOp) + { + switch (pipelineOp.PipelineOpType) + { + case PipelineOpType.AddEntity: + block.AddEntity((AddEntityPipelineOp) pipelineOp); + break; + case PipelineOpType.RemoveEntity: + block.RemoveEntity((RemoveEntityPipelineOp) pipelineOp); + break; + case PipelineOpType.CriticalSection: + block.CriticalSection((CriticalSectionPipelineOp) pipelineOp); + break; + case PipelineOpType.AddComponent: + block.AddComponent((AddComponentPipelineOp) pipelineOp); + break; + case PipelineOpType.RemoveComponent: + block.RemoveComponent((RemoveComponentPipelineOp) pipelineOp); + break; + case PipelineOpType.ChangeAuthority: + block.ChangeAuthority((ChangeAuthorityPipelineOp) pipelineOp); + break; + case PipelineOpType.UpdateComponent: + block.UpdateComponent((UpdateComponentPipelineOp) pipelineOp); + break; + default: + throw new ArgumentException(string.Format("Unknown op type {0}, cannot dispatch.", pipelineOp.GetType().Name), "op"); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineExtensions.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineExtensions.cs.meta new file mode 100644 index 0000000..ac12e62 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2451cf7427a53df4c98ae951389342b3 +timeCreated: 1491560323 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineOps.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineOps.cs new file mode 100644 index 0000000..fa0214f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineOps.cs @@ -0,0 +1,228 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Worker; + +namespace Improbable.Unity.Core +{ + /// + /// The possible types for a pipeline op object. + /// + public enum PipelineOpType + { + AddEntity, + RemoveEntity, + CriticalSection, + AddComponent, + RemoveComponent, + ChangeAuthority, + UpdateComponent + } + + // + /// Common parent interface for all pipeline ops. + /// + public interface IEntityPipelineOp + { + // + /// Type of the pipeline op. + /// + PipelineOpType PipelineOpType { get; } + } + + /// + /// Pipeline op emitted when an entity is added to the worker. + /// + public struct AddEntityPipelineOp : IEntityPipelineOp + { + /// + /// Dispatcher op corresponding to the pipeline op. + /// + public AddEntityOp DispatcherOp; + + /// + /// Entity id of corresponding entity. + /// + public EntityId EntityId + { + get { return DispatcherOp.EntityId; } + } + + /// + public PipelineOpType PipelineOpType + { + get { return PipelineOpType.AddEntity; } + } + } + + /// + /// Pipeline op emitted when an entity is removed from the worker. + /// + public struct RemoveEntityPipelineOp : IEntityPipelineOp + { + /// + /// Dispatcher op corresponding to the pipeline op. + /// + public RemoveEntityOp DispatcherOp; + + /// + /// Entity id of corresponding entity. + /// + public EntityId EntityId + { + get { return DispatcherOp.EntityId; } + } + + /// + public PipelineOpType PipelineOpType + { + get { return PipelineOpType.RemoveEntity; } + } + } + + /// + /// Pipeline Op emitted when the worker enters/exits a critical section. + /// + public struct CriticalSectionPipelineOp : IEntityPipelineOp + { + /// + /// Dispatcher op corresponding to the pipeline op. + /// + public CriticalSectionOp DispatcherOp; + + /// + /// Indicate whether worker is about to enter a critical section (true) or exit a critical section (false). + /// + public bool InCriticalSection + { + get { return DispatcherOp.InCriticalSection; } + } + + /// + public PipelineOpType PipelineOpType + { + get { return PipelineOpType.CriticalSection; } + } + } + + /// + /// Pipeline op emitted when a SpatialOS component is added to an entity. + /// + public struct AddComponentPipelineOp : IEntityPipelineOp + { + /// + /// Id of the SpatialOS entity the component was added to. + /// + public EntityId EntityId; + + /// + /// The object that corresponds to the affected SpatialOS component. + /// + public object ComponentObject; + + /// + /// Type of the added component. + /// + public IComponentMetaclass ComponentMetaClass; + + /// + public PipelineOpType PipelineOpType + { + get { return PipelineOpType.AddComponent; } + } + } + + /// + /// Pipeline op emitted when a SpatialOS component is removed from an entity. + /// + public struct RemoveComponentPipelineOp : IEntityPipelineOp + { + /// + /// Id of the SpatialOS entity the component was removed from. + /// + public EntityId EntityId; + + /// + /// The object that corresponds to the affected SpatialOS component. + /// + public object ComponentObject; + + /// + /// Type of the removed component. + /// + public IComponentMetaclass ComponentMetaClass; + + /// + public PipelineOpType PipelineOpType + { + get { return PipelineOpType.RemoveComponent; } + } + } + + /// + /// Pipeline op emitted when authority for a SpatialOS component changes. + /// + public struct ChangeAuthorityPipelineOp : IEntityPipelineOp + { + /// + /// Id of the SpatialOS entity on which the authority over a component changed. + /// + public EntityId EntityId; + + /// + /// The object that corresponds to the affected SpatialOS component. + /// + public object ComponentObject; + + /// + /// Type of the affected component. + /// + public IComponentMetaclass ComponentMetaClass; + + /// + /// Indicates whether the worker received authority over the component. + /// + [System.Obsolete("Please use \"Authority == Improbable.Worker.Authority.Authoritative || Authority == Improbable.Worker.Authority.AuthorityLossImminent\".")] + public bool HasAuthority + { + get { return Authority == Authority.Authoritative || Authority == Authority.AuthorityLossImminent; } + } + + /// + /// Indicates the state of authority over the component. + /// + public Authority Authority; + + /// + public PipelineOpType PipelineOpType + { + get { return PipelineOpType.ChangeAuthority; } + } + } + + /// + /// Pipeline op emitted when a component update is received. + /// + public struct UpdateComponentPipelineOp : IEntityPipelineOp + { + /// + /// Id of the SpatialOS entity that is receiving the component update. + /// + public EntityId EntityId; + + /// + /// The update object that corresponds to the affected SpatialOS component. + /// + public object UpdateObject; + + /// + /// Type of the affected component. + /// + public IComponentMetaclass ComponentMetaClass; + + /// + public PipelineOpType PipelineOpType + { + get { return PipelineOpType.UpdateComponent; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineOps.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineOps.cs.meta new file mode 100644 index 0000000..d4a128c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/EntityPipelineOps.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 08e9e4beb88d36d40adbcfc68767fd77 +timeCreated: 1493034647 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipeline.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipeline.cs new file mode 100644 index 0000000..c9b8b3c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipeline.cs @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// Logic for handling operations related to the lifetime + /// of SpatialOS entity and component objects. + /// + public interface IEntityPipeline + { + /// + /// Adds a processing block at the end of the pipeline. + /// + IEntityPipeline AddBlock(IEntityPipelineBlock block); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipeline.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipeline.cs.meta new file mode 100644 index 0000000..4aa8917 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipeline.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 759f3c9b510ea0848b0bb80f8880c15b +timeCreated: 1492701020 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineBlock.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineBlock.cs new file mode 100644 index 0000000..739427a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineBlock.cs @@ -0,0 +1,95 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// Operational block of the + /// + public interface IEntityPipelineBlock + { + /// + /// Called when is received by the worker. + /// + /// + /// The op will not be automatically passed to the next block. + /// Use .(addEntityOp) + /// to pass the op to the next block in the pipeline. + /// + void AddEntity(AddEntityPipelineOp addEntityOp); + + /// + /// Called when is received by the worker. + /// + /// + /// The op will not be automatically passed to the next block. + /// Use .(removeEntityOp) + /// to pass the op to the next block in the pipeline. + /// + void RemoveEntity(RemoveEntityPipelineOp removeEntityOp); + + /// + /// Called when is received by the worker. + /// + /// + /// The op will not be automatically passed to the next block. + /// Use .(criticalSectionOp) + /// to pass the op to the next block in the pipeline. + /// + void CriticalSection(CriticalSectionPipelineOp criticalSectionOp); + + /// + /// Called when is received by the worker. + /// + /// + /// The op will not be automatically passed to the next block. + /// Use .(addComponentOp) + /// to pass the op to the next block in the pipeline. + /// + void AddComponent(AddComponentPipelineOp addComponentOp); + + /// + /// Called when is received by the worker. + /// + /// + /// The op will not be automatically passed to the next block. + /// Use .(removeComponentOp) + /// to pass the op to the next block in the pipeline. + /// + void RemoveComponent(RemoveComponentPipelineOp removeComponentOp); + + /// + /// Called when is received by the worker. + /// + /// + /// The op will not be automatically passed to the next block. + /// Use .(changeAuthorityOp) + /// to pass the op to the next block in the pipeline. + /// + void ChangeAuthority(ChangeAuthorityPipelineOp changeAuthorityOp); + + /// + /// Called when is received by the worker. + /// + /// + /// The op will not be automatically passed to the next block. + /// Use .(updateComponentOp) + /// to pass the op to the next block in the pipeline. + /// + void UpdateComponent(UpdateComponentPipelineOp updateComponentOp); + + /// + /// Method called every frame that allows the block to execute logic dependent + /// on its internal state. + /// + void ProcessOps(); + + /// + /// Reference to the next block in the pipeline. + /// + /// + /// Guaranteed to be non-null when block is registered with + /// and the worker is connected. + /// + IEntityPipelineBlock NextEntityBlock { get; set; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineBlock.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineBlock.cs.meta new file mode 100644 index 0000000..1c3321d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineBlock.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1347baea8863fca4f9be4dd7eeb0e811 +timeCreated: 1493034647 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineInternal.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineInternal.cs new file mode 100644 index 0000000..b37645b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineInternal.cs @@ -0,0 +1,46 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Worker; + +namespace Improbable.Unity.Core +{ + /// + /// + /// Internal interface of the entity pipeline. + /// + /// + /// Should only be used from within the SDK. + /// + interface IEntityPipelineInternal : IEntityPipeline, IDisposable + { + /// + /// Process any buffered ops. + /// + /// + /// Needs to be called regularly (e.g. every frame) for the ops + /// to progress through the pipeline. + /// + void ProcessOps(); + + /// + /// Enables the processing of ops inside the pipeline. + /// + void Start(ISpatialCommunicator spatialCommunicator); + + /// + /// Registers the pipeline as the receiver of component-related ops. + /// + void RegisterComponentFactories(Connection connection, Dispatcher dispatcher); + + /// + /// Unregisters the pipeline as the receiver of component-related ops. + /// + void UnregisterComponentFactories(); + + /// + /// True if the pipeline contains any processing blocks. + /// + bool IsEmpty { get; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineInternal.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineInternal.cs.meta new file mode 100644 index 0000000..3ad5c95 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/IEntityPipelineInternal.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 149b6494174070d4f9149dd1c484f518 +timeCreated: 1493034647 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/PassOpsBlock.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/PassOpsBlock.cs new file mode 100644 index 0000000..2acc708 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/PassOpsBlock.cs @@ -0,0 +1,64 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core +{ + /// + /// Entity pipeline block that passes through all operations. + /// + /// + /// You may use this as a base class to avoid boilerplate code when + /// writing entity pipeline blocks that only process some of the + /// op streams exposed via the interface. + /// The class is abstract, as it does nothing meaningful on its own. + /// + public abstract class PassOpsBlock : IEntityPipelineBlock + { + /// + public virtual void AddEntity(AddEntityPipelineOp addEntity) + { + NextEntityBlock.AddEntity(addEntity); + } + + /// + public virtual void RemoveEntity(RemoveEntityPipelineOp removeEntityOp) + { + NextEntityBlock.RemoveEntity(removeEntityOp); + } + + /// + public virtual void CriticalSection(CriticalSectionPipelineOp criticalSectionOp) + { + NextEntityBlock.CriticalSection(criticalSectionOp); + } + + /// + public virtual void AddComponent(AddComponentPipelineOp addComponentOp) + { + NextEntityBlock.AddComponent(addComponentOp); + } + + /// + public virtual void RemoveComponent(RemoveComponentPipelineOp removeComponentOp) + { + NextEntityBlock.RemoveComponent(removeComponentOp); + } + + /// + public virtual void ChangeAuthority(ChangeAuthorityPipelineOp changeAuthorityOp) + { + NextEntityBlock.ChangeAuthority(changeAuthorityOp); + } + + /// + public virtual void UpdateComponent(UpdateComponentPipelineOp updateComponentOp) + { + NextEntityBlock.UpdateComponent(updateComponentOp); + } + + /// + public virtual void ProcessOps() { } + + /// + public IEntityPipelineBlock NextEntityBlock { get; set; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/PassOpsBlock.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/PassOpsBlock.cs.meta new file mode 100644 index 0000000..3a1a431 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityPipeline/PassOpsBlock.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e1f0eadf09767c94fa1e31686c128b8d +timeCreated: 1491560323 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries.meta new file mode 100644 index 0000000..b179e49 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9dfa0042c69621f4894d5bd38f7a2d22 +folderAsset: yes +timeCreated: 1515488809 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/CreateEntityResult.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/CreateEntityResult.cs new file mode 100644 index 0000000..0f6f155 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/CreateEntityResult.cs @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core.EntityQueries +{ + /// + /// Contains the result of CreateEntity command. + /// + public struct CreateEntityResult + { + private readonly EntityId createdEntityId; + + public CreateEntityResult(EntityId createdEntityId) : this() + { + this.createdEntityId = createdEntityId; + } + + /// + /// Returns the EntityId of the created entity. + /// + public EntityId CreatedEntityId + { + get { return createdEntityId; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/CreateEntityResult.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/CreateEntityResult.cs.meta new file mode 100644 index 0000000..c0fabdd --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/CreateEntityResult.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 480f74b8b9d6a2045812afe2976ebce3 +timeCreated: 1497352606 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/DeleteEntityResult.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/DeleteEntityResult.cs new file mode 100644 index 0000000..57a1ace --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/DeleteEntityResult.cs @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core.EntityQueries +{ + /// + /// Contains the result of DeleteEntity command. + /// + public struct DeleteEntityResult + { + private readonly EntityId deletedEntityId; + + public DeleteEntityResult(EntityId deletedEntityId) : this() + { + this.deletedEntityId = deletedEntityId; + } + + /// + /// Returns the EntityId that was deleted. + /// + public EntityId DeletedEntityId + { + get { return deletedEntityId; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/DeleteEntityResult.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/DeleteEntityResult.cs.meta new file mode 100644 index 0000000..75a6d62 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/DeleteEntityResult.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 3da01da974000fe46981a78ab36f23b4 +timeCreated: 1497352606 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/EntityQueryResult.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/EntityQueryResult.cs new file mode 100644 index 0000000..200240d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/EntityQueryResult.cs @@ -0,0 +1,38 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Collections; + +namespace Improbable.Unity.Core.EntityQueries +{ + /// + /// Contains the result of a query. + /// + public struct EntityQueryResult + { + private readonly int entityCount; + private readonly Map entities; + + public EntityQueryResult(int entityCount, Map entities) + { + this.entityCount = entityCount; + this.entities = entities; + } + + /// + /// Returns the number of entities in the result set. + /// + public int EntityCount + { + get { return entityCount; } + } + + /// + /// Returns a map of entities keyed by their entity ids. + /// Empty if the query was just for a count. + /// + public Map Entities + { + get { return entities; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/EntityQueryResult.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/EntityQueryResult.cs.meta new file mode 100644 index 0000000..b51feee --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/EntityQueryResult.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 4b923987246598547a04b7d3738437d5 +timeCreated: 1445005141 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/ReserveEntityIdResult.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/ReserveEntityIdResult.cs new file mode 100644 index 0000000..0b6f9dd --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/ReserveEntityIdResult.cs @@ -0,0 +1,25 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Core.EntityQueries +{ + /// + /// Contains the result of ReserveEntityId command. + /// + public struct ReserveEntityIdResult + { + private readonly EntityId reservedEntityId; + + public ReserveEntityIdResult(EntityId reservedEntityId) : this() + { + this.reservedEntityId = reservedEntityId; + } + + /// + /// Returns the EntityId that was reserved. + /// + public EntityId ReservedEntityId + { + get { return reservedEntityId; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/ReserveEntityIdResult.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/ReserveEntityIdResult.cs.meta new file mode 100644 index 0000000..119040a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityQueries/ReserveEntityIdResult.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 25178e5b642b9c64ca2baf5eb99a36a8 +timeCreated: 1497352606 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityReservation.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityReservation.meta new file mode 100644 index 0000000..52215ee --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityReservation.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 12fc11ee4bbfd6749afd9ed5040186e2 +folderAsset: yes +timeCreated: 1511369158 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityReservation/ReserveEntityIdsResult.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityReservation/ReserveEntityIdsResult.cs new file mode 100644 index 0000000..9c44d6d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityReservation/ReserveEntityIdsResult.cs @@ -0,0 +1,36 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Improbable.Unity.Core.EntityQueries +{ + /// + /// Contains the result of ReserveEntityIds command. + /// + public struct ReserveEntityIdsResult + { + private readonly ReadOnlyCollection reservedEntityIds; + + public ReserveEntityIdsResult(EntityId firstEntityId, int numberOfEntityIds) : this() + { + var newReservedEntityIds = new List(numberOfEntityIds); + + for (var i = 0; i < numberOfEntityIds; ++i) + { + newReservedEntityIds.Add(new EntityId(firstEntityId.Id + i)); + } + + reservedEntityIds = newReservedEntityIds.AsReadOnly(); + } + + /// + /// Returns a contiguous range of newly allocated entity IDs which are guaranteed to be unused in the current + /// deployment. + /// + public ReadOnlyCollection ReservedEntityIds + { + get { return reservedEntityIds; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityReservation/ReserveEntityIdsResult.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityReservation/ReserveEntityIdsResult.cs.meta new file mode 100644 index 0000000..96306dc --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/EntityReservation/ReserveEntityIdsResult.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 589c83d7c4a5d4b4e8c35bfa722f64da +timeCreated: 1506012008 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export.meta new file mode 100644 index 0000000..cf59367 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9a7c92528534afd4891d75748acbcdc6 +folderAsset: yes +timeCreated: 1484562303 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/IPrefabExportProcessor.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/IPrefabExportProcessor.cs new file mode 100644 index 0000000..5505a9a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/IPrefabExportProcessor.cs @@ -0,0 +1,9 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Export +{ + public interface IPrefabExportProcessor + { + void ExportProcess(WorkerPlatform worker); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/IPrefabExportProcessor.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/IPrefabExportProcessor.cs.meta new file mode 100644 index 0000000..87f7cf1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/IPrefabExportProcessor.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8ab1a4237944fa94285fb9af128a96c7 +timeCreated: 1484562303 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/KeepOnExportedPrefabAttribute.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/KeepOnExportedPrefabAttribute.cs new file mode 100644 index 0000000..d91b5cc --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/KeepOnExportedPrefabAttribute.cs @@ -0,0 +1,8 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.Export +{ + public class KeepOnExportedPrefabAttribute : Attribute { } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/KeepOnExportedPrefabAttribute.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/KeepOnExportedPrefabAttribute.cs.meta new file mode 100644 index 0000000..a75611e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Export/KeepOnExportedPrefabAttribute.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2834306ee2597d440afe78bd3d50082d +timeCreated: 1484562303 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline.meta new file mode 100644 index 0000000..470bc4f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 4da4a6baac68f499d8002fc21c47406d +folderAsset: yes +timeCreated: 1493115734 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/AssetPreloader.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/AssetPreloader.cs new file mode 100644 index 0000000..269dcce --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/AssetPreloader.cs @@ -0,0 +1,118 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Improbable.Unity.Assets; +using Improbable.Unity.ComponentFactory; +using Improbable.Unity.Entity; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + class AssetPreloader : IDisposable + { + private readonly PooledPrefabFactory prefabPool; + private readonly IEntityTemplateProvider templateProvider; + private readonly IList assetsToPrecache; + + private readonly IEnumerable> assetsToPrePool; + + private readonly ConcurrentAssetPrecacher downloader; + + private bool downloadCompleted; + + public event Action PrecachingCompleted; + public event Action PrecachingProgress; + + public AssetPreloader(MonoBehaviour hostBehaviour, IEntityTemplateProvider templateProvider, IList assetsToPrecache, IEnumerable> assetsToPrePool, int maxConcurrentPrecacheConnections) + { + this.templateProvider = templateProvider; + + this.prefabPool = new PooledPrefabFactory(); + this.assetsToPrecache = assetsToPrecache; + this.assetsToPrePool = assetsToPrePool; + + downloader = new ConcurrentAssetPrecacher( + hostBehaviour, + assetsToPrecache, + OnPrecachingCompleted, + OnPrecachingProgress, + templateProvider, + maxConcurrentPrecacheConnections); + } + + private void OnPrecachingCompleted() + { + if (PrecachingCompleted != null) + { + PrecachingCompleted(); + } + + downloadCompleted = true; + } + + private void OnPrecachingProgress(int progress) + { + if (PrecachingProgress != null) + { + PrecachingProgress(progress); + } + } + + public IEnumerator PrepareAssets() + { + PrePoolAssets(); + if (assetsToPrecache != null && assetsToPrecache.Count > 0) + { + var precache = PrecacheAssets(); + yield return precache; + } + + yield return null; + } + + private IEnumerator PrecacheAssets() + { + downloader.BeginPrecaching(); + + var wait = new WaitUntil(() => downloadCompleted); + yield return wait; + } + + private void PrePoolAssets() + { + if (assetsToPrePool == null || !assetsToPrePool.Any()) + { + return; + } + + foreach (var prefabNameToCount in assetsToPrePool) + { + // Prepooling only supports default context at the moment. Ultimately all this code should be pulled out into user code. + var requestedCountInPool = prefabNameToCount.Value; + templateProvider.PrepareTemplate(prefabNameToCount.Key, prefabName => + { + var prefab = templateProvider.GetEntityTemplate(prefabName); + prefabPool.PreparePool(prefab, prefabName, requestedCountInPool); + }, + exception => Debug.LogErrorFormat("Problem initialising pool for entity {0}: {1}", + prefabNameToCount.Key, exception.Message)); + } + } + + public IPrefabFactory PrefabFactory + { + get { return prefabPool; } + } + + public void Dispose() + { + if (prefabPool != null) + { + prefabPool.Dispose(); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/AssetPreloader.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/AssetPreloader.cs.meta new file mode 100644 index 0000000..907ecfe --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/AssetPreloader.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 05bc67afa8e79f64c8f69da891c3d2ee +timeCreated: 1491493737 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory.meta new file mode 100644 index 0000000..e6072eb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 088706a4137a5cb4c9eebde0216bae3d +folderAsset: yes +timeCreated: 1444833350 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabContainer.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabContainer.cs new file mode 100644 index 0000000..c999c60 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabContainer.cs @@ -0,0 +1,208 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Improbable.Unity.ComponentFactory +{ + public class PooledPrefabContainer : MonoBehaviour, IDisposable + { + private const int PoolLayer = 31; + + private GameObject LoadedPrefab; + private string prefabName; + + private int InstanceNumber = 1; + + private readonly Dictionary SpawnedObjects = new Dictionary(); + private readonly List DespawnedObjects = new List(); + + public void Init(GameObject prefab, string prefabName) + { + AddSelfToPoolLayer(); + LoadedPrefab = prefab; + this.prefabName = prefabName; + name = string.Format("[Pool] {0}", prefabName); + } + + public void Dispose() + { + DespawnAllObjects(); + DestroyDespawnedObjects(); + } + + private void DespawnAllObjects() + { + var reportedProblem = false; + + // Gather all spawned objects to an array as the SpawnedObjects dictionary will be modified with Despawn + var spawnedObjects = SpawnedObjects.Values.ToArray(); + + foreach (var spawnedObject in spawnedObjects) + { + try + { + Despawn(spawnedObject.GameObject); + } + catch (Exception e) + { + if (!reportedProblem) + { + reportedProblem = true; + + Debug.LogErrorFormat("Failed to despawn object for prefab {0} (container {1}). This can happen when spawned objects are destroyed by user code.", prefabName, name); + + Debug.LogException(e); + } + } + } + + if (SpawnedObjects.Count != 0) + { + Debug.LogWarningFormat("Not all pooled objects for prefab {0} have been despawned (container {1}).", + prefabName, name); + } + } + + private void DestroyDespawnedObjects() + { + var reportedProblem = false; + + foreach (var despawnedObject in DespawnedObjects) + { + try + { + Destroy(despawnedObject.GameObject); + } + catch (Exception e) + { + if (!reportedProblem) + { + reportedProblem = true; + + Debug.LogErrorFormat("Failed to destroy object for prefab {0} (container {1}). This can happen when spawned objects are destroyed by user code.", prefabName, name); + + Debug.LogException(e); + } + } + } + + DespawnedObjects.Clear(); + } + + private void AddSelfToPoolLayer() + { + gameObject.layer = PoolLayer; + } + + public static bool IsPool(GameObject obj) + { + return obj.layer == PoolLayer; + } + + public void Despawn(GameObject spawnedObject) + { + PooledObject pooled; + if (SpawnedObjects.TryGetValue(spawnedObject, out pooled)) + { + pooled.DespawnedOnFrame = Time.frameCount; + SpawnedObjects.Remove(spawnedObject); + if (SetDespawned(spawnedObject)) + { + DespawnedObjects.Add(pooled); + } + } + else + { + Debug.LogWarningFormat("Could not despawn {0} (prefab {1})", spawnedObject.name, prefabName); + } + } + + public GameObject Spawn(Vector3 position, Quaternion rotation) + { + var freeObject = FindExistingObject() ?? CreateNewObject(); + InitObject(position, rotation, freeObject); + return freeObject.GameObject; + } + + public bool Contains(GameObject obj) + { + return SpawnedObjects.ContainsKey(obj); + } + + public int ActiveCount + { + get { return SpawnedObjects.Count; } + } + + private PooledObject CreateNewObject() + { + return new PooledObject(LoadedPrefab) { GameObject = { name = string.Format("{0} {1:#000}", prefabName, InstanceNumber++) } }; + } + + private bool SetDespawned(GameObject spawnedObject) + { + try + { + spawnedObject.transform.SetParent(transform); + spawnedObject.SetActive(false); + return true; + } + catch (Exception e) + { + Debug.LogErrorFormat("Failed to despawn object for prefab {0} (container {1}). This can happen when spawned objects are destroyed by user code.", prefabName, name); + Debug.LogException(e); + return false; + } + } + + public void CreateInactiveInPool() + { + var pooled = CreateNewObject(); + if (SetDespawned(pooled.GameObject)) + { + DespawnedObjects.Add(pooled); + } + } + + private void InitObject(Vector3 position, Quaternion rotation, PooledObject pooledObject) + { + pooledObject.GameObject.transform.SetPositionAndRotation(position, rotation); + pooledObject.GameObject.transform.SetParent(transform); + + SpawnedObjects[pooledObject.GameObject] = pooledObject; + + pooledObject.GameObject.SetActive(true); + } + + private PooledObject FindExistingObject() + { + // Entities that were despawned within the last 2 frames are ignored to ensure that they aren't + // re-used before all of their scheduled cleanup operations are completely finished + for (var index = 0; index < DespawnedObjects.Count; index++) + { + var obj = DespawnedObjects[index]; + if (Time.frameCount - obj.DespawnedOnFrame >= 2) + { + DespawnedObjects.RemoveAt(index); + return obj; + } + } + + return null; + } + + private class PooledObject + { + public PooledObject(GameObject loadedPrefab) + { + GameObject = (GameObject) Instantiate(loadedPrefab); + } + + public GameObject GameObject { get; private set; } + public int DespawnedOnFrame { get; set; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabContainer.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabContainer.cs.meta new file mode 100644 index 0000000..7b5066d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabContainer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: cfe162ea6a685e042a9ebd2e26306dc1 +timeCreated: 1444833362 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabFactory.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabFactory.cs new file mode 100644 index 0000000..a149150 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabFactory.cs @@ -0,0 +1,212 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Entity; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Improbable.Unity.ComponentFactory +{ + public class PooledPrefabFactory : IPrefabFactory, IDisposable + { + private readonly Dictionary> OutOfDatePools = new Dictionary>(); + private readonly Dictionary Pools = new Dictionary(); + public static readonly Vector3 InstantiationPoint = new Vector3(-9999, -9999, -9999); + + public GameObject MakeComponent(GameObject loadedPrefab, string prefabName) + { + return Spawn(loadedPrefab, prefabName, InstantiationPoint, Quaternion.identity); + } + + public GameObject MakeComponent(GameObject prefab, string prefabName, Vector3 position, Quaternion rotation) + { + return Spawn(prefab, prefabName, position, rotation); + } + + public void DespawnComponent(GameObject gameObject, string prefabName) + { + Despawn(gameObject, prefabName); + } + + public void InvalidatePool(string prefabName) + { + if (!Pools.ContainsKey(prefabName)) + { + return; + } + + var currentPool = Pools[prefabName]; + Pools.Remove(prefabName); + + MarkPoolAsInvalid(prefabName, currentPool); + } + + public void PreparePool(GameObject loadedPrefab, string prefabName, int count) + { + var poolComponent = GetOrCreatePool(loadedPrefab, prefabName); + + for (var i = 0; i < count; ++i) + { + poolComponent.CreateInactiveInPool(); + } + } + + private GameObject Spawn(GameObject loadedPrefab, string prefabName, Vector3 position, Quaternion rotation) + { + var pool = GetOrCreatePool(loadedPrefab, prefabName); + return pool.Spawn(position, rotation); + } + + private void Despawn(GameObject pooledGameObject, string prefabName) + { + GameObject pool; + if (Pools.TryGetValue(prefabName, out pool)) + { + var container = pool.GetComponent(); + if (container.Contains(pooledGameObject)) + { + container.Despawn(pooledGameObject); + return; + } + } + + DespawnFromOldPools(pooledGameObject, prefabName); + } + + private void DespawnFromOldPools(GameObject pooledGameObject, string prefabName) + { + List oldPools; + if (!OutOfDatePools.TryGetValue(prefabName, out oldPools)) + { + return; + } + + foreach (var pool in oldPools) + { + var container = pool.GetComponent(); + if (container.Contains(pooledGameObject)) + { + container.Despawn(pooledGameObject); + if (container.ActiveCount == 0) + { + oldPools.Remove(pool); + Object.Destroy(pool); + } + + return; + } + } + } + + private void MarkPoolAsInvalid(string prefabName, GameObject pool) + { + List oldPools; + if (!OutOfDatePools.TryGetValue(prefabName, out oldPools)) + { + oldPools = new List(); + OutOfDatePools.Add(prefabName, oldPools); + } + + oldPools.Add(pool); + } + + private PooledPrefabContainer GetOrCreatePool(GameObject loadedPrefab, string prefabName) + { + GameObject pool; + if (Pools.TryGetValue(prefabName, out pool)) + { + return pool.GetComponent(); + } + + return CreatePool(loadedPrefab, prefabName); + } + + private PooledPrefabContainer CreatePool(GameObject loadedPrefab, string prefabName) + { + var pool = new GameObject(); + Pools[prefabName] = pool; + + var poolComponent = pool.AddComponent(); + poolComponent.Init(loadedPrefab, prefabName); + + return poolComponent; + } + + private static void DestroyPool(GameObject pool) + { + var poolPrefabContainer = pool.GetComponent(); + if (poolPrefabContainer != null) + { + poolPrefabContainer.Dispose(); + } + + Object.Destroy(pool); + } + + private void DisposeOfActivePools() + { + var reportedProblem = false; + + // Destroy all active pools and spawned objects + foreach (var pool in Pools.Values) + { + try + { + DestroyPool(pool); + } + catch (Exception e) + { + if (!reportedProblem) + { + reportedProblem = true; + Debug.LogError("Failed to destroy active prefab pools. This can happen when pool objects are destroyed by user code."); + + Debug.LogException(e); + } + } + } + + Pools.Clear(); + } + + private void DisposeOfOutOfDatePools() + { + // Prevent repetition of errors if + var reportedProblem = false; + + // Destroy all inactive pools, as they can still contain spawned objects + foreach (var poolList in OutOfDatePools.Values) + { + foreach (var pool in poolList) + { + try + { + DestroyPool(pool); + } + catch (Exception e) + { + if (!reportedProblem) + { + reportedProblem = true; + Debug.LogError("Failed to destroy inactive prefab pools. This can happen when pool objects are destroyed by user code."); + + Debug.LogException(e); + } + } + } + + poolList.Clear(); + } + + OutOfDatePools.Clear(); + } + + public void Dispose() + { + DisposeOfActivePools(); + + DisposeOfOutOfDatePools(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabFactory.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabFactory.cs.meta new file mode 100644 index 0000000..cd31ca9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PooledPrefabFactory.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 12eec9d4d073265489fe01b725b678e8 +timeCreated: 1444833356 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PrefabFactoryMetrics.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PrefabFactoryMetrics.cs new file mode 100644 index 0000000..c43d471 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PrefabFactoryMetrics.cs @@ -0,0 +1,48 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Entity; +using Improbable.Unity.Metrics; +using UnityEngine; + +namespace Improbable.Unity.ComponentFactory +{ + /// + /// A Proxy to wrap a IPrefabFactroy such that we report metrics about + /// the number of entities in a worker by prefab + /// metrics are named "prefab.{prefabName}.count" + /// + public class PrefabFactoryMetrics : IPrefabFactory + { + private readonly IPrefabFactory prefabFactory; + private readonly WorkerMetrics metrics; + + public PrefabFactoryMetrics(IPrefabFactory prefabFactory, WorkerMetrics metrics) + { + this.prefabFactory = prefabFactory; + this.metrics = metrics; + } + + /// + public GameObject MakeComponent(GameObject prefabGameObject, string prefabName) + { + var name = GetPrefabsGauge(prefabName); + metrics.IncrementGauge(name); + + return prefabFactory.MakeComponent(prefabGameObject, prefabName); + } + + /// + public void DespawnComponent(GameObject gameObject, string prefabName) + { + var name = GetPrefabsGauge(prefabName); + metrics.DecrementGauge(name); + + prefabFactory.DespawnComponent(gameObject, prefabName); + } + + private string GetPrefabsGauge(string prefabName) + { + return "prefab." + prefabName + ".count"; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PrefabFactoryMetrics.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PrefabFactoryMetrics.cs.meta new file mode 100644 index 0000000..f43ce02 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/PrefabFactoryMetrics.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c1870b9f7d6a67248a42e652821728fd +timeCreated: 1444833361 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/UnityPrefabFactory.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/UnityPrefabFactory.cs new file mode 100644 index 0000000..6e3a5c1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/UnityPrefabFactory.cs @@ -0,0 +1,29 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Entity; +using UnityEngine; + +namespace Improbable.Unity.ComponentFactory +{ + public class UnityPrefabFactory : IPrefabFactory + { + private static readonly Vector3 InstantiationPoint = new Vector3(-9999, -9999, -9999); + + public GameObject MakeComponent(GameObject loadedPrefab, string prefabName) + { + return Object.Instantiate(loadedPrefab, InstantiationPoint, Quaternion.identity); + } + + public void DespawnComponent(GameObject gameObject, string prefabName) + { + if (Application.isEditor && !Application.isPlaying) + { + Object.DestroyImmediate(gameObject); + } + else + { + Object.Destroy(gameObject); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/UnityPrefabFactory.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/UnityPrefabFactory.cs.meta new file mode 100644 index 0000000..bd91cd6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ComponentFactory/UnityPrefabFactory.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 54779d4f2b6da514ebd52b0ed490264f +timeCreated: 1444833358 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ConcurrentAssetPrecacher.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ConcurrentAssetPrecacher.cs new file mode 100644 index 0000000..747b13e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ConcurrentAssetPrecacher.cs @@ -0,0 +1,106 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections; +using System.Collections.Generic; +using Improbable.Unity.Entity; +using UnityEngine; + +namespace Improbable.Unity.Assets +{ + class ConcurrentAssetPrecacher + { + private readonly MonoBehaviour hostMonoBehaviour; + + private int completedPrecachedCount; + private int concurrentDownloads; + private int nextAssetToPrecacheIndex; + + private readonly IList assetsToPrecache; + private readonly int maxConcurrentConnections; + private readonly Action onCompleted; + private readonly Action onProgress; + private readonly IEntityTemplateProvider entityTemplateProvider; + + public ConcurrentAssetPrecacher(MonoBehaviour hostMonoBehaviour, IList assetsToPrecache, Action onCompleted, Action onProgress, IEntityTemplateProvider entityTemplateProvider, int concurrentConnections = 5) + { + this.hostMonoBehaviour = hostMonoBehaviour; + this.assetsToPrecache = assetsToPrecache; + this.onCompleted = onCompleted; + this.onProgress = onProgress; + this.entityTemplateProvider = entityTemplateProvider; + + maxConcurrentConnections = concurrentConnections; + } + + public void BeginPrecaching() + { + if (maxConcurrentConnections < 1) + { + throw new ArgumentException("Maximum concurrent connections must be greater than 0."); + } + + hostMonoBehaviour.StartCoroutine(Precache()); + } + + private IEnumerator Precache() + { + ReportProgress(); + while (completedPrecachedCount < assetsToPrecache.Count) + { + while (nextAssetToPrecacheIndex < assetsToPrecache.Count && concurrentDownloads < maxConcurrentConnections) + { + StartPrecachingAsset(); + } + + yield return null; + } + + if (onCompleted != null) + { + onCompleted(); + } + } + + private void StartPrecachingAsset() + { + var assetToPrecache = assetsToPrecache[nextAssetToPrecacheIndex]; + OnAssetPrecacheStarted(assetToPrecache); + entityTemplateProvider.PrepareTemplate(assetToPrecache, + OnAssetPrecached, + ex => OnAssetPrecacheFailed(assetToPrecache, ex)); + } + + private void OnAssetPrecacheStarted(string prefabName) + { + ++nextAssetToPrecacheIndex; + ++concurrentDownloads; + } + + private void OnAssetPrecached(string prefabName) + { + OnPrecachingAssetCompleted(); + } + + private void OnAssetPrecacheFailed(string prefabName, Exception ex) + { + OnPrecachingAssetCompleted(); + Debug.LogErrorFormat("Failed to precache asset '{0}.\nException: {1}", prefabName, ex); + } + + private void OnPrecachingAssetCompleted() + { + ++completedPrecachedCount; + --concurrentDownloads; + ReportProgress(); + } + + private void ReportProgress() + { + if (onProgress != null) + { + onProgress(completedPrecachedCount); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ConcurrentAssetPrecacher.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ConcurrentAssetPrecacher.cs.meta new file mode 100644 index 0000000..06edf2e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/LegacyEntityPipeline/ConcurrentAssetPrecacher.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1301f97c44e84e849986e958e44125e9 +timeCreated: 1444833356 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging.meta new file mode 100644 index 0000000..2e3a77e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a6f99405e353e9044ab85947cdb4eefc +folderAsset: yes +timeCreated: 1444833351 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/ILogFilterReceiver.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/ILogFilterReceiver.cs new file mode 100644 index 0000000..c17da04 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/ILogFilterReceiver.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Unity.Logging +{ + /// + /// Filter log messages that are sent to SpatialOS. + /// + public interface ILogFilterReceiver + { + /// + /// Tell Unity SDK if it should send a log message or not. + /// + LogAction FilterLogMessage(string logString, string stackTrace, LogType type); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/ILogFilterReceiver.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/ILogFilterReceiver.cs.meta new file mode 100644 index 0000000..0b25da0 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/ILogFilterReceiver.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 93d8274239729f747a910bcbaa00071c +timeCreated: 1517826156 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/Log.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/Log.cs new file mode 100644 index 0000000..5beb04f --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/Log.cs @@ -0,0 +1,115 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Collections; +using Improbable.Unity.Configuration; +using Improbable.Worker; +using UnityEngine; + +namespace Improbable.Unity.Logging +{ + /// + /// Helper for IoC in tests. + /// + internal delegate void MessageSender(LogLevel level, string loggerName, string message, Option entityId); + + internal class LogSender : IDisposable, ILogFilterReceiver + { + private readonly Func isConnected; + private readonly MessageSender sender; + private readonly WorkerConfiguration configuration; + private readonly ILogFilterReceiver filter; + + public LogSender(Func isConnected, MessageSender sender, WorkerConfiguration configuration, ILogFilterReceiver filter) + { + if (sender == null) + { + throw new ArgumentNullException("sender"); + } + + if (isConnected == null) + { + throw new ArgumentNullException("isConnected"); + } + + this.isConnected = isConnected; + this.sender = sender; + this.configuration = configuration; + this.filter = filter ?? this; + + Application.logMessageReceived += ApplicationOnLogMessageReceived; + } + + public LogAction FilterLogMessage(string logstring, string stacktrace, LogType type) + { + return LogAction.SendIfAllowed; + } + + public void Dispose() + { + Application.logMessageReceived -= ApplicationOnLogMessageReceived; + } + + private void ApplicationOnLogMessageReceived(string logString, string stackTrace, LogType type) + { + if (!isConnected()) + { + return; + } + + var action = filter.FilterLogMessage(logString, stackTrace, type); + + LogLevel logLevel; + bool shouldSendLog; + switch (type) + { + case LogType.Log: + shouldSendLog = configuration.LogDebugToSpatialOs; + logLevel = LogLevel.Info; + break; + case LogType.Assert: + shouldSendLog = configuration.LogAssertToSpatialOs; + logLevel = LogLevel.Error; + break; + case LogType.Warning: + shouldSendLog = configuration.LogWarningToSpatialOs; + logLevel = LogLevel.Warn; + break; + case LogType.Error: + shouldSendLog = configuration.LogErrorToSpatialOs; + logLevel = LogLevel.Error; + break; + case LogType.Exception: + shouldSendLog = configuration.LogExceptionToSpatialOs; + logLevel = LogLevel.Error; + break; + default: + shouldSendLog = configuration.LogDebugToSpatialOs; + logLevel = LogLevel.Info; + break; + } + + switch (action) + { + case LogAction.SendIfAllowed: + break; + case LogAction.SendAlways: + // Override the blanket setting from the configuration and send it onwards. + shouldSendLog = true; + break; + case LogAction.DontSend: + shouldSendLog = false; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + if (!shouldSendLog) + { + return; + } + + sender(logLevel, "Unity", string.Format("{0}\n in {1}", logString, stackTrace), null); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/Log.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/Log.cs.meta new file mode 100644 index 0000000..2012cb9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/Log.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 07f29b55daadfac4b8f37342f9a8e0d4 +timeCreated: 1517564828 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogAction.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogAction.cs new file mode 100644 index 0000000..bdc8050 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogAction.cs @@ -0,0 +1,24 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Logging +{ + /// + /// + public enum LogAction + { + /// + /// Send the log message to SpatialOS if the allows it. + /// + SendIfAllowed, + + /// + /// Send the log messsage, even if is configured not to send it. + /// + SendAlways, + + /// + /// Do not send the log message to SpatialOS. + /// + DontSend + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogAction.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogAction.cs.meta new file mode 100644 index 0000000..c274a32 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogAction.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1343d5170cda7ec4ab9e6eab327d1f18 +timeCreated: 1517564828 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogListener.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogListener.cs new file mode 100644 index 0000000..60616a6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogListener.cs @@ -0,0 +1,21 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using UnityEngine; + +namespace Improbable.Unity.Logging +{ + /// + /// Report exceptions to the logger. + /// + /// + /// for a method of filtering log messages. + [Obsolete("This class no longer does anything and will be removed. Please see SpatialOS.LogFilter and SpatialOS.LogFormatter for more information.")] + public class LogListener : MonoBehaviour + { + public void OnEnable() + { + Debug.LogWarningFormat("{0} is no longer used. Please see the documentation for information about configuring your log messages.", typeof(LogListener)); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogListener.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogListener.cs.meta new file mode 100644 index 0000000..48eb635 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Logging/LogListener.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6a1b3aa61c49ae645a77ad0d35dd6644 +timeCreated: 1444833358 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics.meta new file mode 100644 index 0000000..1fd3712 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 0d2580e801aadf649b264c0e4e8c3287 +folderAsset: yes +timeCreated: 1457363998 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/EndTimer.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/EndTimer.cs new file mode 100644 index 0000000..31bfd63 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/EndTimer.cs @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Metrics +{ + class EndTimer : MonoBehaviour + { + public ScriptLifecycleAnalytics Analytics { set; private get; } + + private void FixedUpdate() + { + Analytics.EndFixedUpdate(); + } + + private void Update() + { + Analytics.EndUpdate(); + } + + private void LateUpdate() + { + Analytics.EndLateUpdate(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/EndTimer.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/EndTimer.cs.meta new file mode 100644 index 0000000..b2d73b8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/EndTimer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 3b0560575dffad74cbe3e9e4e4806aba +timeCreated: 1458228021 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 32000 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ILoadMetricProvider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ILoadMetricProvider.cs new file mode 100644 index 0000000..c3953fe --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ILoadMetricProvider.cs @@ -0,0 +1,9 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +namespace Improbable.Unity.Metrics +{ + public interface ILoadMetricProvider + { + double Load { get; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ILoadMetricProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ILoadMetricProvider.cs.meta new file mode 100644 index 0000000..3a0d869 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ILoadMetricProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 1bd66e93a2fb70d41820af15750be948 +timeCreated: 1473164847 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/MetricsReporter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/MetricsReporter.cs new file mode 100644 index 0000000..e59c863 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/MetricsReporter.cs @@ -0,0 +1,38 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System.Diagnostics; +using Improbable.Metrics; +using Improbable.Unity.Core; +using Improbable.Unity.Metrics; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + class MetricsReporter : MonoBehaviour + { + private const long UpdateMinPeriodMillis = 2000; + private readonly Stopwatch stopwatch = new Stopwatch(); + private long startedAt; + private ILoadMetricProvider loadMetricProvider; + + public void Start() + { + stopwatch.Start(); + loadMetricProvider = gameObject.GetComponent() ?? gameObject.AddComponent(); + } + + public void Update() + { + var currentTime = stopwatch.ElapsedMilliseconds; + var elapsed = currentTime - startedAt; + + if (SpatialOS.IsConnected && elapsed > UpdateMinPeriodMillis) + { + SpatialOS.Metrics.SetLoad(loadMetricProvider.Load); + SpatialOS.Connection.SendMetrics(SpatialOS.Metrics.RawMetrics); + startedAt = currentTime; + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/MetricsReporter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/MetricsReporter.cs.meta new file mode 100644 index 0000000..b3f356d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/MetricsReporter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 9e94475b22cea2f45a728991af0822bf +timeCreated: 1469547247 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleAnalytics.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleAnalytics.cs new file mode 100644 index 0000000..ad2dc87 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleAnalytics.cs @@ -0,0 +1,259 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections; +using System.Diagnostics; +using UnityEngine; + +namespace Improbable.Metrics +{ + /// + /// This class provides analytics of the Unity Script execution loop. + /// The exposed metrics report the total amount of time spent in the + /// relevant section since the start of measurements, and the amount + /// of time spent when given section was last executed. All reported + /// numbers are in seconds. + /// Rendering and its relevant metrics assume that Unity does not sleep + /// in between FixedFrames. + /// http://docs.unity3d.com/Manual/ExecutionOrder.html + /// Note: this class is a MonoBehaviour so that its dependencies can + /// be managed easily. + /// + class ScriptLifecycleAnalytics : MonoBehaviour + { + private readonly Stopwatch watch = new Stopwatch(); + private readonly WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame(); + + private long startOfFixedUpdate; + private long startOfUpdate; + private long startOfLateUpdate; + + private long lastFixedUpdateDuration; + private long lastUpdateDuration; + private long lastLateUpdateDuration; + private bool shouldCalculatePhysicsUpdateDuration; + private long lastPhysicsDuration; + private long lastAnimationUpdateDuration; + private long lastRenderDuration; + + private long cumulativeFixedUpdateDuration; + private long cumulativeUpdateDuration; + private long cumulativeLateUpdateDuration; + private long cumulativePhysicsDuration; + private long cumulativeAnimationUpdateDuration; + private long cumulativeRenderDuration; + + public long FixedFrameCount { get; private set; } + public long FrameCount { get; private set; } + + /// Time in seconds spent during last call to FixedUpdate. + public float LastFixedUpdateDuration() + { + return (float) lastFixedUpdateDuration / Stopwatch.Frequency; + } + + /// Time in seconds spent during all calls to FixedUpdate since start of game. + public float CumulativeFixedUpdateDuration() + { + return (float) cumulativeFixedUpdateDuration / Stopwatch.Frequency; + } + + /// Time in seconds spent during last call to Update. + public float LastUpdateDuration() + { + return (float) lastUpdateDuration / Stopwatch.Frequency; + } + + /// Time in seconds spent during all calls to Update since start of game. + public float CumulativeUpdateDuration() + { + return (float) cumulativeUpdateDuration / Stopwatch.Frequency; + } + + /// Time in seconds spent during last call to LateUpdate. + public float LastLateUpdateDuration() + { + return (float) lastLateUpdateDuration / Stopwatch.Frequency; + } + + /// Time in seconds spent during all calls to LateUpdate since start of game. + public float CumulativeLateUpdateDuration() + { + return (float) cumulativeLateUpdateDuration / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during last physics calculation. + /// Includes calls to WaitForFixedUpdate, OnTrigger and OnCollision. + /// + public float LastPhysicsDuration() + { + return (float) lastPhysicsDuration / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during all physics calculations since start of game. + /// Includes calls to WaitForFixedUpdate, OnTrigger and OnCollision. + /// + public float CumulativePhysicsDuration() + { + return (float) cumulativePhysicsDuration / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during last Internal animation update. + /// Includes calls to yield {null, WaitForSeconds, WWW, and StartCoroutine}. + /// + public float LastAnimationUpdateDuration() + { + return (float) lastAnimationUpdateDuration / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during all Internal animation updates since start of game. + /// Includes calls to yield {null, WaitForSeconds, WWW, and StartCoroutine}. + /// + public float CumulativeAnimationUpdateDuration() + { + return (float) cumulativeAnimationUpdateDuration / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during last Scene rendering. + /// Includes calls to OnDrawGizmos and OnGUI. + /// + public float LastRenderDuration() + { + return (float) lastRenderDuration / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during all Scene renderings since start of game. + /// Includes calls to OnDrawGizmos and OnGUI. + /// + public float CumulativeRenderDuration() + { + return (float) cumulativeRenderDuration / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during last Physics calculation loop. + /// See http://docs.unity3d.com/Manual/ExecutionOrder.html for reference. + /// + public float LastPhysicsLoopDuration() + { + return (float) (lastFixedUpdateDuration + lastPhysicsDuration) / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during all Physics calculation loops since start of game. + /// See http://docs.unity3d.com/Manual/ExecutionOrder.html for reference. + /// + public float CumulativePhysicsLoopDuration() + { + return (float) (cumulativeFixedUpdateDuration + cumulativePhysicsDuration) / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during last Rendering calculation loop, + /// excluding the inner Physics loop. + /// See http://docs.unity3d.com/Manual/ExecutionOrder.html for reference. + /// + public float LastRenderLoopDuration() + { + return (float) (lastUpdateDuration + lastAnimationUpdateDuration + lastLateUpdateDuration + lastRenderDuration) / Stopwatch.Frequency; + } + + /// + /// Time in seconds spent during all Rendering calculation loops since start of game, + /// excluding the inner Physics loop. + /// See http://docs.unity3d.com/Manual/ExecutionOrder.html for reference. + /// + public float CumulativeRenderLoopDuration() + { + return (float) (cumulativeUpdateDuration + cumulativeAnimationUpdateDuration + cumulativeLateUpdateDuration + cumulativeRenderDuration) / Stopwatch.Frequency; + } + + private void OnEnable() + { + var startTimer = gameObject.GetComponent() ?? gameObject.AddComponent(); + startTimer.Analytics = this; + startTimer.enabled = true; + + var endTimer = gameObject.GetComponent() ?? gameObject.AddComponent(); + endTimer.Analytics = this; + endTimer.enabled = true; + + watch.Start(); + } + + private void OnDisable() + { + watch.Stop(); + gameObject.GetComponent().enabled = false; + gameObject.GetComponent().enabled = false; + } + + internal void StartFixedUpdate() + { + FixedFrameCount++; + var now = watch.ElapsedTicks; + + if (shouldCalculatePhysicsUpdateDuration) + { + shouldCalculatePhysicsUpdateDuration = false; + lastPhysicsDuration = now - lastFixedUpdateDuration - startOfFixedUpdate; + cumulativePhysicsDuration += lastPhysicsDuration; + } + + startOfFixedUpdate = now; + } + + internal void EndFixedUpdate() + { + lastFixedUpdateDuration = watch.ElapsedTicks - startOfFixedUpdate; + cumulativeFixedUpdateDuration += lastFixedUpdateDuration; + shouldCalculatePhysicsUpdateDuration = true; + } + + internal void StartUpdate() + { + startOfUpdate = watch.ElapsedTicks; + FrameCount++; + + if (shouldCalculatePhysicsUpdateDuration) + { + shouldCalculatePhysicsUpdateDuration = false; + lastPhysicsDuration = startOfUpdate - lastFixedUpdateDuration - startOfFixedUpdate; + cumulativePhysicsDuration += lastPhysicsDuration; + } + } + + internal void EndUpdate() + { + lastUpdateDuration = watch.ElapsedTicks - startOfUpdate; + cumulativeUpdateDuration += lastUpdateDuration; + } + + internal void StartLateUpdate() + { + startOfLateUpdate = watch.ElapsedTicks; + lastAnimationUpdateDuration = startOfLateUpdate - lastUpdateDuration - startOfUpdate; + cumulativeAnimationUpdateDuration += lastAnimationUpdateDuration; + } + + internal void EndLateUpdate() + { + lastLateUpdateDuration = watch.ElapsedTicks - startOfLateUpdate; + cumulativeLateUpdateDuration += lastLateUpdateDuration; + StartCoroutine(EndOfFrame()); + } + + private IEnumerator EndOfFrame() + { + yield return waitForEndOfFrame; + + lastRenderDuration = watch.ElapsedTicks - lastLateUpdateDuration - startOfLateUpdate; + cumulativeRenderDuration += lastRenderDuration; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleAnalytics.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleAnalytics.cs.meta new file mode 100644 index 0000000..a932df6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleAnalytics.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 23ec88979300913418089a1ce988d692 +timeCreated: 1458227640 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleLoadMetricProvider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleLoadMetricProvider.cs new file mode 100644 index 0000000..08a3511 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleLoadMetricProvider.cs @@ -0,0 +1,95 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System.Diagnostics; +using Improbable.Unity.Metrics; +using UnityEngine; + +namespace Improbable.Metrics +{ + /// + /// Provides an estimation of the load of the Unity worker based on analysis of + /// the Unity Script Lifecycle http://docs.unity3d.com/Manual/ExecutionOrder.html + /// Uses targetFrameRate and target fixed frame rate (Time.fixedDeltaTime / Time.timeScale) + /// to estimate load. + /// + public class ScriptLifecycleLoadMetricProvider : MonoBehaviour, ILoadMetricProvider + { + private ScriptLifecycleAnalytics scriptLifecycleAnalytics; + private long lastPhysicsLoopCount; + private long lastRenderLoopCount; + private float lastPhysicsLoopTime; + private float lastRenderLoopTime; + + private float timePerPhysics; + private float timePerRender; + private long lastReportedTime; + private readonly Stopwatch watch = new Stopwatch(); + + // Only query the load metric once per UpdatePeriodMs milliseconds + public long UpdateMinPeriodMillis = 2000; + + private void Start() + { + watch.Start(); + scriptLifecycleAnalytics = gameObject.GetComponent() ?? gameObject.AddComponent(); + } + + private void Update() + { + if (watch.ElapsedMilliseconds - lastReportedTime > UpdateMinPeriodMillis) + { + lastReportedTime = watch.ElapsedMilliseconds; + Load = GetLoad(); + } + } + + /// + /// A load estimation from the last time this method was called. + /// If called more frequently, will return noisier data. + /// If called before more analytics is present, will return the last recorded load. + /// + private float GetLoad() + { + UpdateTimePerPhysicsLoop(); + UpdateTimePerRenderLoop(); + + var targetRenderCount = Application.targetFrameRate; + var targetPhysicsTime = Time.fixedDeltaTime / Time.timeScale; + + return timePerPhysics / targetPhysicsTime + timePerRender * targetRenderCount; + } + + private void UpdateTimePerPhysicsLoop() + { + var newPhysicsLoopCount = scriptLifecycleAnalytics.FixedFrameCount; + var physicsLoopCountDelta = newPhysicsLoopCount - lastPhysicsLoopCount; + + if (physicsLoopCountDelta > 0) + { + var newPhysicsLoopTime = scriptLifecycleAnalytics.CumulativePhysicsLoopDuration(); + timePerPhysics = (newPhysicsLoopTime - lastPhysicsLoopTime) / physicsLoopCountDelta; + + lastPhysicsLoopTime = newPhysicsLoopTime; + lastPhysicsLoopCount = newPhysicsLoopCount; + } + } + + private void UpdateTimePerRenderLoop() + { + var newRenderLoopCount = scriptLifecycleAnalytics.FrameCount; + var renderLoopCountDelta = newRenderLoopCount - lastRenderLoopCount; + + if (renderLoopCountDelta > 0) + { + var newRenderLoopTime = scriptLifecycleAnalytics.CumulativeRenderLoopDuration(); + timePerRender = (newRenderLoopTime - lastRenderLoopTime) / renderLoopCountDelta; + + lastRenderLoopTime = newRenderLoopTime; + lastRenderLoopCount = newRenderLoopCount; + } + } + + public double Load { get; private set; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleLoadMetricProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleLoadMetricProvider.cs.meta new file mode 100644 index 0000000..a10ddd3 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/ScriptLifecycleLoadMetricProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 74ea0b873ffc2a14697a945df9fe9107 +timeCreated: 1458234755 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/StartTimer.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/StartTimer.cs new file mode 100644 index 0000000..67393d8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/StartTimer.cs @@ -0,0 +1,26 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using UnityEngine; + +namespace Improbable.Metrics +{ + class StartTimer : MonoBehaviour + { + public ScriptLifecycleAnalytics Analytics { set; private get; } + + private void FixedUpdate() + { + Analytics.StartFixedUpdate(); + } + + private void Update() + { + Analytics.StartUpdate(); + } + + private void LateUpdate() + { + Analytics.StartLateUpdate(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/StartTimer.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/StartTimer.cs.meta new file mode 100644 index 0000000..84343f6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/StartTimer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: de0af7e32febd1143bb82e65881a71f0 +timeCreated: 1458227999 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: -32000 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/UnityFixedFrameLoadMetricProvider.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/UnityFixedFrameLoadMetricProvider.cs new file mode 100644 index 0000000..7592f8c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/UnityFixedFrameLoadMetricProvider.cs @@ -0,0 +1,75 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System; +using System.Collections; +using System.Diagnostics; +using Improbable.Unity.Metrics; +using UnityEngine; + +namespace Improbable.Metrics +{ + /// + /// Provide load based on how long a frame takes to render. + /// + public class UnityFixedFrameLoadMetricProvider : MonoBehaviour, ILoadMetricProvider + { + private static readonly WaitForEndOfFrame WaitForEndOfFrame = new WaitForEndOfFrame(); + private Coroutine endOfFrameCoroutine; + + private readonly Stopwatch stopwatch = new Stopwatch(); + private TimeSpan? lastFixedUpdateStart; + + public double Load { get; private set; } + + public void Awake() + { + stopwatch.Start(); + } + + private void OnEnable() + { + endOfFrameCoroutine = StartCoroutine(EndOfFrame()); + } + + private void OnDisable() + { + if (endOfFrameCoroutine != null) + { + StopCoroutine(endOfFrameCoroutine); + endOfFrameCoroutine = null; + } + } + + public void FixedUpdate() + { + SnapshotTime(); + } + + private void SnapshotTime() + { + if (!lastFixedUpdateStart.HasValue) + { + lastFixedUpdateStart = stopwatch.Elapsed; + } + } + + private IEnumerator EndOfFrame() + { + while (isActiveAndEnabled) + { + yield return WaitForEndOfFrame; + + if (lastFixedUpdateStart.HasValue) + { + var delta = stopwatch.Elapsed - lastFixedUpdateStart.Value; + var maxDeltaTime = 1.0 / Application.targetFrameRate; + + Load = delta.TotalSeconds / maxDeltaTime; + + lastFixedUpdateStart = null; + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/UnityFixedFrameLoadMetricProvider.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/UnityFixedFrameLoadMetricProvider.cs.meta new file mode 100644 index 0000000..6013ef5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/UnityFixedFrameLoadMetricProvider.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d018bc78cce6f7a4d9975f0b789fac1e +timeCreated: 1457364009 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/WorkerMetrics.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/WorkerMetrics.cs new file mode 100644 index 0000000..d48f22a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/WorkerMetrics.cs @@ -0,0 +1,102 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Collections; +using Improbable.Worker; + +namespace Improbable.Unity.Metrics +{ + /// + /// This class is responsible for all of the metrics tracked by the application. + /// + /// + /// It is not safe to access any members of this class from any thread other than the main thread. + /// + public class WorkerMetrics + { + private readonly Worker.Metrics instance = new Worker.Metrics(); + + /// + /// Provides access to the underlying metrics object. + /// + public Worker.Metrics RawMetrics + { + get { return instance; } + } + + /// + /// Provides access to user-defined gauges. + /// + public Map Gauges + { + get { return instance.GaugeMetrics; } + } + + /// + /// Provides access to user-defined histograms. + /// + public Map Histograms + { + get { return instance.HistogramMetrics; } + } + + /// + /// Sets the current worker load metric. + /// + /// + public void SetLoad(double load) + { + instance.Load = load; + } + + /// + /// Increments the specified gauge by 1. + /// + /// + /// If a gauge does not already exist, a new zeroed gauge will be created and then incremented. + /// + public void IncrementGauge(string gaugeName) + { + IncrementGaugeBy(gaugeName, 1); + } + + /// + /// Decrements the specified gauge by 1. + /// + /// + /// If a gauge does not already exist, a new zeroed gauge will be created and then incremented. + /// + public void DecrementGauge(string gaugeName) + { + IncrementGaugeBy(gaugeName, -1); + } + + /// + /// Sets the specified gauge to the value. + /// + /// + /// If a gauge does not already exist, a new zeroed gauge will be created. + /// + public void SetGauge(string gaugeName, double newValue) + { + instance.GaugeMetrics[gaugeName] = newValue; + } + + /// + /// Increments the specified gauge by the specified amount. + /// + /// + /// If a gauge does not already exist, a new zeroed gauge will be created and then incremented. + /// + public void IncrementGaugeBy(string gaugeName, int amount) + { + if (instance.GaugeMetrics.ContainsKey(gaugeName)) + { + instance.GaugeMetrics[gaugeName] += amount; + } + else + { + instance.GaugeMetrics[gaugeName] = amount; + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/WorkerMetrics.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/WorkerMetrics.cs.meta new file mode 100644 index 0000000..58a976d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Metrics/WorkerMetrics.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 400a252a88dc2564091780b7f06fc4b4 +timeCreated: 1469797241 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons.meta new file mode 100644 index 0000000..05b2991 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 87badbfb60e7bf24f92879998cb3c47c +folderAsset: yes +timeCreated: 1495101829 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline.meta new file mode 100644 index 0000000..1a84651 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 359d352c3870977438d9e32c641596d7 +folderAsset: yes +timeCreated: 1496410795 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/AuthorityChangedNotifier.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/AuthorityChangedNotifier.cs new file mode 100644 index 0000000..6469406 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/AuthorityChangedNotifier.cs @@ -0,0 +1,73 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Entity; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Notifies the registered objects + /// about received authority changes. + /// + class AuthorityChangedNotifier : PassOpsBlock + { + private readonly ILocalEntities localEntities; + private readonly IList changeReceivers = new List(); + + public AuthorityChangedNotifier(ILocalEntities localEntities) + { + this.localEntities = localEntities; + } + + /// + /// Registers an object to receive callbacks on component authority changes. + /// + public void RegisterAuthorityChangedReceiver(IAuthorityChangedReceiver receiver) + { + if (receiver == null) + { + throw new ArgumentNullException("receiver"); + } + + changeReceivers.Add(receiver); + } + + /// + /// Removes an object from the collection of authority change callback receivers. + /// + /// True if the suppled object was registered as a receiver and was removed. + public bool TryRemoveAuthorityChangedReceiver(IAuthorityChangedReceiver receiver) + { + return changeReceivers.Remove(receiver); + } + + /// + public override void ChangeAuthority(ChangeAuthorityPipelineOp op) + { + var entity = localEntities.Get(op.EntityId); + if (entity != null) + { + var visualizers = entity.Visualizers as EntityVisualizers; + if (visualizers != null) + { + visualizers.OnAuthorityChanged(op.ComponentMetaClass, op.Authority, op.ComponentObject); + InvokeOnAuthorityChangedCallbacks(op); + } + } + else + { + Debug.LogErrorFormat("Entity not found: OnAuthorityChanged({0}, {1}, {2})", op.EntityId, op.ComponentMetaClass, op.Authority); + } + } + + private void InvokeOnAuthorityChangedCallbacks(ChangeAuthorityPipelineOp op) + { + for (var i = 0; i < changeReceivers.Count; i++) + { + changeReceivers[i].AuthorityChanged(op.EntityId, op.ComponentMetaClass, op.Authority, op.ComponentObject); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/AuthorityChangedNotifier.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/AuthorityChangedNotifier.cs.meta new file mode 100644 index 0000000..432b1eb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/AuthorityChangedNotifier.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 37f5357f2b54ea34b9c8036ff17be5b6 +timeCreated: 1491560323 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/CriticalSectionPipelineBlock.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/CriticalSectionPipelineBlock.cs new file mode 100644 index 0000000..b07d89e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/CriticalSectionPipelineBlock.cs @@ -0,0 +1,115 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Entity pipeline block that blocks all pipeline ops from continuing + /// through the pipeline until the end of the critical section is reached. + /// At this point all of the ops within the critical section are + /// dispatched sequentially to the next pipeline block. Subsequent pipeline + /// blocks will not receive any critical section ops. + /// + class CriticalSectionPipelineBlock : IEntityPipelineBlock + { + private bool inCriticalSection; + + private readonly Queue criticalSectionEntityOps = new Queue(); + + /// + public void AddEntity(AddEntityPipelineOp addEntityOp) + { + if (!inCriticalSection) + { + Debug.LogErrorFormat("CriticalSectionPipelineBlock received AddEntityPipelineOp for entity {0} outside of critical section", addEntityOp.EntityId); + return; + } + + criticalSectionEntityOps.Enqueue(addEntityOp); + } + + /// + public void RemoveEntity(RemoveEntityPipelineOp removeEntityOp) + { + StallForCriticalSection(removeEntityOp); + } + + /// + public void CriticalSection(CriticalSectionPipelineOp criticalSectionOp) + { + if (inCriticalSection) + { + if (criticalSectionOp.InCriticalSection) + { + Debug.LogError("CriticalSectionPipelineBlock received critical section start during another critical section"); + } + else + { + inCriticalSection = false; + ReleaseCriticalSectionOps(); + } + } + else + { + if (criticalSectionOp.InCriticalSection) + { + inCriticalSection = true; + } + else + { + Debug.LogError("CriticalSectionPipelineBlock received critical section stop outside a critical section"); + } + } + } + + private void ReleaseCriticalSectionOps() + { + while (criticalSectionEntityOps.Count > 0) + { + NextEntityBlock.DispatchOp(criticalSectionEntityOps.Dequeue()); + } + } + + /// + public void AddComponent(AddComponentPipelineOp addComponentOp) + { + StallForCriticalSection(addComponentOp); + } + + /// + public void RemoveComponent(RemoveComponentPipelineOp removeComponentOp) + { + StallForCriticalSection(removeComponentOp); + } + + /// + public void ChangeAuthority(ChangeAuthorityPipelineOp changeAuthorityOp) + { + StallForCriticalSection(changeAuthorityOp); + } + + /// + public void UpdateComponent(UpdateComponentPipelineOp updateComponentOp) + { + StallForCriticalSection(updateComponentOp); + } + + private void StallForCriticalSection(IEntityPipelineOp entityPipelineOp) + { + if (!inCriticalSection) + { + NextEntityBlock.DispatchOp(entityPipelineOp); + return; + } + + criticalSectionEntityOps.Enqueue(entityPipelineOp); + } + + /// + public void ProcessOps() { } + + public IEntityPipelineBlock NextEntityBlock { get; set; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/CriticalSectionPipelineBlock.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/CriticalSectionPipelineBlock.cs.meta new file mode 100644 index 0000000..7798bb7 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/CriticalSectionPipelineBlock.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: aabe264b4b1498445a6d652c842d8268 +timeCreated: 1498154484 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/EntityComponentUpdater.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/EntityComponentUpdater.cs new file mode 100644 index 0000000..ba33f37 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/EntityComponentUpdater.cs @@ -0,0 +1,80 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Entity; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Pipeline block for applying dispatcher ops to generated MonoBehaviour components. + /// + class EntityComponentUpdater : PassOpsBlock + { + private readonly IUniverse universe; + + public EntityComponentUpdater(IUniverse universe) + { + this.universe = universe; + } + + /// + public override void AddComponent(AddComponentPipelineOp addComponentOp) + { + var entityId = addComponentOp.EntityId; + if (!universe.ContainsEntity(entityId)) + { + Debug.LogError("EntityComponentUpdater::AddComponent: Entity not present: " + entityId); + return; + } + + var entity = universe.Get(entityId); + entity.OnAddComponentPipelineOp(addComponentOp); + NextEntityBlock.AddComponent(addComponentOp); + } + + /// + public override void RemoveComponent(RemoveComponentPipelineOp removeComponentOp) + { + var entityId = removeComponentOp.EntityId; + if (!universe.ContainsEntity(entityId)) + { + Debug.LogError("EntityComponentUpdater::RemoveComponent: Entity not present: " + entityId); + return; + } + + var entity = universe.Get(entityId); + entity.OnRemoveComponentPipelineOp(removeComponentOp); + NextEntityBlock.RemoveComponent(removeComponentOp); + } + + /// + public override void ChangeAuthority(ChangeAuthorityPipelineOp changeAuthorityOp) + { + var entityId = changeAuthorityOp.EntityId; + if (!universe.ContainsEntity(entityId)) + { + Debug.LogError("EntityComponentUpdater::ChangeAuthority: Entity not present: " + entityId); + return; + } + + var entity = universe.Get(entityId); + entity.OnAuthorityChangePipelineOp(changeAuthorityOp); + NextEntityBlock.ChangeAuthority(changeAuthorityOp); + } + + /// + public override void UpdateComponent(UpdateComponentPipelineOp updateComponentOp) + { + var entityId = updateComponentOp.EntityId; + if (!universe.ContainsEntity(entityId)) + { + Debug.LogError("EntityComponentUpdater::UpdateComponent: Entity not present: " + entityId); + return; + } + + var entity = universe.Get(entityId); + entity.OnComponentUpdatePipelineOp(updateComponentOp); + NextEntityBlock.UpdateComponent(updateComponentOp); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/EntityComponentUpdater.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/EntityComponentUpdater.cs.meta new file mode 100644 index 0000000..18ac1da --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/EntityComponentUpdater.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f4559eb36412f8b49b214b86924e7479 +timeCreated: 1497536467 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ILegacyEntityPipelineConfiguration.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ILegacyEntityPipelineConfiguration.cs new file mode 100644 index 0000000..e6009c1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ILegacyEntityPipelineConfiguration.cs @@ -0,0 +1,73 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Entity; +using Improbable.Unity.Metrics; + +namespace Improbable.Unity.Core +{ + /// + /// Everything that is needed to run the legacy asset pipeline. + /// + /// + /// This de-couples the LegacyAssetPipeline from SpatialOS utility class. + /// + public interface ILegacyEntityPipelineConfiguration + { + /// + /// EntityTemplateProvider to be used. + /// + IEntityTemplateProvider TemplateProvider { get; } + + /// + /// True if prefab pooling should be used. + /// + bool UsePrefabPooling { get; } + + /// + /// List of assets to be precached. + /// + IList AssetsToPrecache { get; } + + /// + /// List of assets to be prepooled. + /// + IEnumerable> AssetsToPrePool { get; } + + /// + /// The maximum number of concurrent connections to be used for asset preaching. + /// + int MaxConcurrentPrecacheConnections { get; } + + /// + /// Delegate to be invoked when asset precaching is complete. + /// + Action OnPrecachingCompleted { get; } + + /// + /// Delegate to be invoked with asset precaching progress. + /// + Action OnPrecacheProgress { get; } + + /// + /// The maximum number of entities that can be created per frame. + /// + int EntityCreationLimitPerFrame { get; } + + /// + /// Metrics object to be used for reportinhg. + /// + WorkerMetrics Metrics { get; } + + /// + /// Class updating interested components. + /// + IEntityComponentInterestOverridesUpdater EntityComponentInterestOverridesUpdater { get; } + + /// + /// Class providing interested components provider. + /// + IInterestedComponentUpdaterProvider InterestedComponentUpdaterProvider { get; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ILegacyEntityPipelineConfiguration.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ILegacyEntityPipelineConfiguration.cs.meta new file mode 100644 index 0000000..364a03d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ILegacyEntityPipelineConfiguration.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 23658a29650e8e9448113085fa988e50 +timeCreated: 1493715108 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyComponentPipeline.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyComponentPipeline.cs new file mode 100644 index 0000000..7c7a172 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyComponentPipeline.cs @@ -0,0 +1,67 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Entity; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Pre SpatialOS 10.4 implementation of the Entity Pipeline. + /// + class LegacyComponentPipeline : PassOpsBlock + { + private readonly IMutableUniverse universe; + + public LegacyComponentPipeline(IMutableUniverse universe) + { + this.universe = universe; + } + + #region EntityEventHandler + + /// + public override void AddComponent(AddComponentPipelineOp addComponentOp) + { + var entity = universe.Get(addComponentOp.EntityId); + if (entity != null) + { + if (entity.Visualizers is EntityVisualizers) + { + ((EntityVisualizers) entity.Visualizers).OnComponentAdded(addComponentOp.ComponentMetaClass, addComponentOp.ComponentObject); + } + } + else + { + Debug.LogErrorFormat("Entity not found: OnComponentAdded({0}, {1})", addComponentOp.EntityId, addComponentOp.ComponentMetaClass); + } + + NextEntityBlock.AddComponent(addComponentOp); + } + + /// + public override void RemoveComponent(RemoveComponentPipelineOp removeComponentOp) + { + NextEntityBlock.RemoveComponent(removeComponentOp); + var entity = universe.Get(removeComponentOp.EntityId); + if (entity != null) + { + if (entity.Visualizers is EntityVisualizers) + { + ((EntityVisualizers) entity.Visualizers).OnComponentRemoved(removeComponentOp.ComponentMetaClass, removeComponentOp.ComponentObject); + } + } + else + { + Debug.LogErrorFormat("Entity not found: OnComponentRemoved({0}, {1})", removeComponentOp.EntityId, removeComponentOp.ComponentMetaClass); + } + } + + /// + public override void ChangeAuthority(ChangeAuthorityPipelineOp changeAuthorityOp) + { + NextEntityBlock.ChangeAuthority(changeAuthorityOp); + } + + #endregion + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyComponentPipeline.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyComponentPipeline.cs.meta new file mode 100644 index 0000000..d015b0b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyComponentPipeline.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: a27bf0c8102ce4c979033bf2835fc027 +timeCreated: 1496417743 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityCreator.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityCreator.cs new file mode 100644 index 0000000..273281b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityCreator.cs @@ -0,0 +1,335 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.Linq; +using Improbable.Unity.ComponentFactory; +using Improbable.Unity.Entity; +using Improbable.Unity.Metrics; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace Improbable.Unity.Core +{ + /// + /// Pre SpatialOS 10.4 implementation of the Entity Pipeline. + /// + class LegacyEntityCreator : IEntityPipelineBlock, IDisposable + { + private class EntitySpawnData + { + internal string EntityType { get; set; } + internal PositionData? Position { get; set; } + internal readonly Queue StalledOps; + internal bool IsLoading { get; set; } + + internal EntitySpawnData() + { + StalledOps = new Queue(); + } + } + + private const string EntityCountGaugeName = "Entity Count"; + + private readonly IEntityTemplateProvider templateProvider; + private readonly IPrefabFactory prefabFactory; + private readonly ISpatialCommunicator spatialCommunicator; + + private readonly IMutableUniverse universe; + private readonly IEntityComponentInterestOverridesUpdater entityComponentInterestOverridesUpdater; + private readonly IInterestedComponentUpdaterProvider interestedComponentUpdaterProvider; + + private readonly HashSet knownEntities; + private readonly IDictionary entitiesToSpawn; + + private readonly WorkerMetrics metrics; + + // The scene that Entities will always be spawned into. + private readonly Scene entitySpawningScene = SceneManager.GetActiveScene(); + + private bool disposed; + + public LegacyEntityCreator(IEntityTemplateProvider templateProvider, + ISpatialCommunicator spatialCommunicator, + IPrefabFactory prefabFactory, + IMutableUniverse universe, + IEntityComponentInterestOverridesUpdater entityComponentInterestOverridesUpdater, + IInterestedComponentUpdaterProvider interestedComponentUpdaterProvider, + WorkerMetrics metrics) + { + this.templateProvider = templateProvider; + this.prefabFactory = new PrefabFactoryMetrics(prefabFactory, metrics); // Associate metrics with the factory + this.spatialCommunicator = spatialCommunicator; + this.universe = universe; + this.entityComponentInterestOverridesUpdater = entityComponentInterestOverridesUpdater; + this.interestedComponentUpdaterProvider = interestedComponentUpdaterProvider; + + entitiesToSpawn = new Dictionary(); + knownEntities = new HashSet(); + + this.metrics = metrics; + } + + #region EntityEventHandler + + /// + public void AddEntity(AddEntityPipelineOp addEntityOp) + { + if (!IsEntityTracked(addEntityOp.EntityId)) + { + knownEntities.Add(addEntityOp.EntityId); + var spawnData = new EntitySpawnData(); + entitiesToSpawn.Add(addEntityOp.EntityId, spawnData); + spawnData.StalledOps.Enqueue(addEntityOp); + metrics.IncrementGauge(EntityCountGaugeName); + } + else + { + Debug.LogErrorFormat("Trying to add the entity '{0}' that already exists.", addEntityOp.EntityId); + } + } + + private void MakeEntity(EntityId entityId, string prefabName, Action onSuccess) + { + entitiesToSpawn[entityId].IsLoading = true; + templateProvider.PrepareTemplate(prefabName, + _ => { onSuccess(entityId, prefabName); }, + exception => { Debug.LogError("Exception occurred when preparing " + prefabName + " template. " + exception); }); + } + + private IEntityObject InstantiateEntityObject(EntityId entityId, string prefabName) + { + // Always ensure that entities are spawned into the same scene that this script was started in. + // This avoids the following case: + // 1) Users dynamically loads scene, activates it, spawns their own entities into it. + // 2) A new entity is spawned by SpatialOS into the new scene. + // 3) The user destroys the scene, also destroying the SpatialOS-created GameObject. + // 4) Errors ensue when the user tries to access the destroyed GameObject via the SpatialOS GameObject tracking layer (e.g. Universe.) + using (new SaveAndRestoreScene(entitySpawningScene)) + { + var loadedPrefab = templateProvider.GetEntityTemplate(prefabName); + var underlyingGameObject = prefabFactory.MakeComponent(loadedPrefab, prefabName); + + var entityObject = new EntityObject(entityId, underlyingGameObject, prefabName, + interestedComponentUpdaterProvider); + + var entityObjectStorage = + underlyingGameObject.GetComponent() ?? + underlyingGameObject.AddComponent(); + + entityObjectStorage.Initialize(entityObject, spatialCommunicator); + + return entityObject; + } + } + + // Releases the entity from this stage of the pipeline + private void ReleaseEntity(EntityId entityId) + { + var entitySpawnData = entitiesToSpawn[entityId]; + while (entitySpawnData.StalledOps.Count > 0) + { + NextEntityBlock.DispatchOp(entitySpawnData.StalledOps.Dequeue()); + } + + entitiesToSpawn.Remove(entityId); + } + + /// + public void RemoveEntity(RemoveEntityPipelineOp removeEntityOp) + { + if (!IsEntityTracked(removeEntityOp.EntityId)) + { + Debug.LogErrorFormat("Trying to destroy an entity we don't have: {0}", removeEntityOp.EntityId); + return; + } + + metrics.DecrementGauge(EntityCountGaugeName); + + if (universe.ContainsEntity(removeEntityOp.EntityId)) + { + NextEntityBlock.RemoveEntity(removeEntityOp); + var entity = universe.Get(removeEntityOp.EntityId); + DestroyEntity(entity); + } + + if (entitiesToSpawn.ContainsKey(removeEntityOp.EntityId)) + { + entitiesToSpawn.Remove(removeEntityOp.EntityId); + } + + knownEntities.Remove(removeEntityOp.EntityId); + } + + private void DestroyEntity(IEntityObject entity) + { + var disposable = entity as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + + entityComponentInterestOverridesUpdater.RemoveEntity(entity); + + universe.Remove(entity.EntityId); + + prefabFactory.DespawnComponent(entity.UnderlyingGameObject, entity.PrefabName); + } + + /// + public void CriticalSection(CriticalSectionPipelineOp criticalSectionOp) { } + + /// + public void AddComponent(AddComponentPipelineOp addComponentOp) + { + if (!IsEntityTracked(addComponentOp.EntityId)) + { + Debug.LogErrorFormat("Entity not found: OnComponentAdded({0}, {1})", addComponentOp.EntityId, + addComponentOp.ComponentMetaClass); + return; + } + + if (universe.ContainsEntity(addComponentOp.EntityId)) + { + NextEntityBlock.AddComponent(addComponentOp); + return; + } + + if (entitiesToSpawn.ContainsKey(addComponentOp.EntityId)) + { + var entityData = entitiesToSpawn[addComponentOp.EntityId]; + + entityData.StalledOps.Enqueue(addComponentOp); + + if (addComponentOp.ComponentMetaClass.ComponentId == Position.ComponentId) + { + entityData.Position = ((Position.Impl) addComponentOp.ComponentObject).Data; + if (entityData.EntityType != null) + { + MakeEntity(addComponentOp.EntityId, entityData.EntityType, OnMakeEntitySuccess); + } + } + else if (addComponentOp.ComponentMetaClass.ComponentId == Metadata.ComponentId) + { + entityData.EntityType = ((Metadata.Impl) addComponentOp.ComponentObject).Data.entityType; + if (entityData.Position.HasValue) + { + MakeEntity(addComponentOp.EntityId, entityData.EntityType, OnMakeEntitySuccess); + } + } + } + } + + private void OnMakeEntitySuccess(EntityId entityId, string prefabName) + { + if (entitiesToSpawn.ContainsKey(entityId)) + { + var entityObject = InstantiateEntityObject(entityId, prefabName); + universe.AddEntity(entityObject); + ReleaseEntity(entityId); + } + } + + /// + public void RemoveComponent(RemoveComponentPipelineOp removeComponentOp) + { + if (!IsEntityTracked(removeComponentOp.EntityId)) + { + Debug.LogErrorFormat("Entity not found: OnComponentAdded({0}, {1})", removeComponentOp.EntityId, + removeComponentOp.ComponentMetaClass); + return; + } + + if (universe.ContainsEntity(removeComponentOp.EntityId)) + { + NextEntityBlock.RemoveComponent(removeComponentOp); + return; + } + + if (entitiesToSpawn.ContainsKey(removeComponentOp.EntityId)) + { + var data = entitiesToSpawn[removeComponentOp.EntityId]; + data.StalledOps.Enqueue(removeComponentOp); + + if (removeComponentOp.ComponentMetaClass.ComponentId == Position.ComponentId) + { + entitiesToSpawn[removeComponentOp.EntityId].Position = null; + } + } + } + + /// + public void ChangeAuthority(ChangeAuthorityPipelineOp changeAuthorityOp) + { + if (!IsEntityTracked(changeAuthorityOp.EntityId)) + { + Debug.LogErrorFormat("Entity not found: OnAuthorityChanged({0}, {1}) authority: {2}", + changeAuthorityOp.EntityId, changeAuthorityOp.ComponentMetaClass, changeAuthorityOp.Authority); + return; + } + + if (universe.ContainsEntity(changeAuthorityOp.EntityId)) + { + NextEntityBlock.ChangeAuthority(changeAuthorityOp); + return; + } + + if (entitiesToSpawn.ContainsKey(changeAuthorityOp.EntityId)) + { + entitiesToSpawn[changeAuthorityOp.EntityId].StalledOps.Enqueue(changeAuthorityOp); + } + } + + /// + public void UpdateComponent(UpdateComponentPipelineOp updateComponentOp) + { + if (!IsEntityTracked(updateComponentOp.EntityId)) + { + Debug.LogErrorFormat("Entity not found: OnComponentUpdate({0}, {1}).", + updateComponentOp.EntityId, updateComponentOp.ComponentMetaClass); + return; + } + + if (universe.ContainsEntity(updateComponentOp.EntityId)) + { + NextEntityBlock.UpdateComponent(updateComponentOp); + return; + } + + if (entitiesToSpawn.ContainsKey(updateComponentOp.EntityId)) + { + entitiesToSpawn[updateComponentOp.EntityId].StalledOps.Enqueue(updateComponentOp); + } + } + + #endregion + + /// + public void ProcessOps() { } + + private void DestroyEntities() + { + foreach (var entityObject in universe.GetAll().ToList()) + { + DestroyEntity(entityObject); + } + } + + public void Dispose() + { + if (!disposed) + { + disposed = true; + DestroyEntities(); + } + } + + /// + public IEntityPipelineBlock NextEntityBlock { get; set; } + + private bool IsEntityTracked(EntityId entityId) + { + return knownEntities.Contains(entityId); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityCreator.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityCreator.cs.meta new file mode 100644 index 0000000..bdb4517 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityCreator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f15933463890a3547999ce6b9d9d437b +timeCreated: 1491560323 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityPipelineSetup.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityPipelineSetup.cs new file mode 100644 index 0000000..7bc902b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityPipelineSetup.cs @@ -0,0 +1,132 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections; +using System.Linq; +using Improbable.Unity.ComponentFactory; +using Improbable.Unity.Entity; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Sets up the legacy entity pipeline implementation. + /// + class LegacyEntityPipelineSetup : IDisposable + { + private readonly IEntityPipeline entityPipeline; + + private readonly AssetPreloader assetPreloader; + private readonly CriticalSectionPipelineBlock criticalSectionPipelineBlock; + private readonly ThrottledEntityDispatcher throttledEntityDispatcher; + private readonly LegacyEntityCreator legacyEntityCreator; + private readonly LegacyComponentPipeline legacyComponentPipeline; + private readonly EntityComponentUpdater entityComponentUpdater; + private IEntityTemplateProvider templateProvider; + + public LegacyEntityPipelineSetup(MonoBehaviour hostBehaviour, IEntityPipeline entityPipeline, ISpatialCommunicator spatialCommunicator, IMutableUniverse universe, ILegacyEntityPipelineConfiguration config, IEntitySpawnLimiter spawnLimiter) + { + this.entityPipeline = entityPipeline; + IPrefabFactory prefabFactory; + + if (!config.UsePrefabPooling && config.AssetsToPrePool != null && config.AssetsToPrePool.Any()) + { + Debug.LogError("There are prefabs specified for pre-pooling, but prefab pooling is not enabled - pooling will occur"); + } + + bool preloaderHasFactory = false; + if (config.UsePrefabPooling || config.AssetsToPrecache != null || config.AssetsToPrePool != null) + { + preloaderHasFactory = true; +#pragma warning disable 0612 + assetPreloader = new AssetPreloader(hostBehaviour, + config.TemplateProvider, + config.AssetsToPrecache, + config.AssetsToPrePool, + config.MaxConcurrentPrecacheConnections); +#pragma warning restore 0612 + assetPreloader.PrecachingCompleted += () => + { + if (config.OnPrecachingCompleted != null) + { + config.OnPrecachingCompleted(); + } + }; + + assetPreloader.PrecachingProgress += progress => + { + if (config.OnPrecacheProgress != null) + { + config.OnPrecacheProgress(progress); + } + }; + } + + if (preloaderHasFactory && config.UsePrefabPooling) + { + prefabFactory = assetPreloader.PrefabFactory; + } + else + { + prefabFactory = new UnityPrefabFactory(); + } + + criticalSectionPipelineBlock = new CriticalSectionPipelineBlock(); + + throttledEntityDispatcher = new ThrottledEntityDispatcher(universe, spawnLimiter, config.Metrics); + + legacyEntityCreator = new LegacyEntityCreator( + config.TemplateProvider, + spatialCommunicator, + prefabFactory, + universe, + config.EntityComponentInterestOverridesUpdater, + config.InterestedComponentUpdaterProvider, + config.Metrics); + + legacyComponentPipeline = new LegacyComponentPipeline(universe); + + entityComponentUpdater = new EntityComponentUpdater(universe); + + templateProvider = config.TemplateProvider; + } + + public void Setup() + { + entityPipeline + .AddBlock(criticalSectionPipelineBlock) + .AddBlock(throttledEntityDispatcher) + .AddBlock(legacyEntityCreator) + .AddBlock(legacyComponentPipeline) + .AddBlock(entityComponentUpdater); + } + + /// + /// Coroutine to prepare assets before establishing connection with SpatialOS. + /// + public IEnumerator PrepareAssets() + { + if (assetPreloader == null) + { + yield break; + } + + yield return assetPreloader.PrepareAssets(); + } + + public void Dispose() + { + legacyEntityCreator.Dispose(); + + if (assetPreloader != null) + { + assetPreloader.Dispose(); + } + + if (templateProvider != null) + { + templateProvider.CancelAllTemplatePreparations(); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityPipelineSetup.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityPipelineSetup.cs.meta new file mode 100644 index 0000000..9acba22 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/LegacyEntityPipelineSetup.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2f7d86215b75bf740a74376d893772b8 +timeCreated: 1493034647 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ThrottledEntityDispatcher.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ThrottledEntityDispatcher.cs new file mode 100644 index 0000000..2440efb --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ThrottledEntityDispatcher.cs @@ -0,0 +1,193 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Unity.Entity; +using Improbable.Unity.Metrics; +using UnityEngine; + +namespace Improbable.Unity.Core +{ + /// + /// Entity pipeline block that limits the number of entity creation + /// ops that can be dispatched between consecutive + /// calls (usually one frame). Queues the ops relevant to deferred entities + /// and releases them once AddEntity ops have been dispatched. + /// + public class ThrottledEntityDispatcher : IEntityPipelineBlock + { + private const string QueueSizeMetricName = "Deferred Entity Creation Queue Size"; + + /// + /// A queue of entities that have been deferred. Order is maintained here so that entities are processed in the order + /// they were originally received. + /// + private readonly Queue entitiesToAdd = new Queue(); + + /// + /// A mapping between an entity and all of its deferred operations. + /// + private readonly Dictionary> entityActions = new Dictionary>(); + + private readonly WorkerMetrics workerMetrics; + + private readonly IUniverse universe; + private readonly IEntitySpawnLimiter spawnLimiter; + + public ThrottledEntityDispatcher(IUniverse universe, IEntitySpawnLimiter spawnLimiter, WorkerMetrics workerMetrics) + { + this.universe = universe; + this.spawnLimiter = spawnLimiter; + this.workerMetrics = workerMetrics; + } + + /// + public void AddEntity(AddEntityPipelineOp addEntityOp) + { + if (spawnLimiter.CanSpawnEntity() && !IsEntityQueued(addEntityOp.EntityId)) + { + NextEntityBlock.AddEntity(addEntityOp); + spawnLimiter.EntityAdded(addEntityOp.EntityId); + } + else + { + Queue queue; + if (!entityActions.TryGetValue(addEntityOp.EntityId, out queue)) + { + queue = entityActions[addEntityOp.EntityId] = new Queue(); + workerMetrics.IncrementGauge(QueueSizeMetricName); + entitiesToAdd.Enqueue(addEntityOp.EntityId); + } + + queue.Enqueue(addEntityOp); + } + } + + /// + public void RemoveEntity(RemoveEntityPipelineOp removeEntityOp) + { + var entityId = removeEntityOp.EntityId; + if (IsEntityQueued(entityId)) + { + entityActions[entityId].Enqueue(removeEntityOp); + } + else + { + NextEntityBlock.RemoveEntity(removeEntityOp); + } + } + + /// + public void CriticalSection(CriticalSectionPipelineOp criticalSectionOp) { } + + /// + public void AddComponent(AddComponentPipelineOp addComponentOp) + { + var entityId = addComponentOp.EntityId; + if (IsEntityQueued(entityId)) + { + entityActions[entityId].Enqueue(addComponentOp); + } + else + { + NextEntityBlock.AddComponent(addComponentOp); + } + } + + /// + public void RemoveComponent(RemoveComponentPipelineOp removeComponentOp) + { + var entityId = removeComponentOp.EntityId; + if (IsEntityQueued(entityId)) + { + entityActions[entityId].Enqueue(removeComponentOp); + } + else + { + NextEntityBlock.RemoveComponent(removeComponentOp); + } + } + + /// + public void ChangeAuthority(ChangeAuthorityPipelineOp changeAuthorityOp) + { + var entityId = changeAuthorityOp.EntityId; + if (IsEntityQueued(entityId)) + { + entityActions[entityId].Enqueue(changeAuthorityOp); + } + else + { + NextEntityBlock.ChangeAuthority(changeAuthorityOp); + } + } + + /// + public void UpdateComponent(UpdateComponentPipelineOp updateComponentOp) + { + var entityId = updateComponentOp.EntityId; + if (IsEntityQueued(entityId)) + { + entityActions[entityId].Enqueue(updateComponentOp); + } + else + { + NextEntityBlock.UpdateComponent(updateComponentOp); + } + } + + private bool IsEntityQueued(EntityId entityId) + { + return entityActions.ContainsKey(entityId); + } + + /// + public void ProcessOps() + { + spawnLimiter.Reset(); + + while (entitiesToAdd.Count > 0) + { + var entityId = entitiesToAdd.Dequeue(); + var actions = entityActions[entityId]; + while (actions.Count > 0) + { + var op = actions.Dequeue(); + try // Ensure that exceptions in user code (e.g. OnEnable) don't disrupt processing of the entire queue. + { + NextEntityBlock.DispatchOp(op); + } + catch (Exception ex) + { + OnError(entityId, ex); + } + } + + entityActions.Remove(entityId); + workerMetrics.DecrementGauge(QueueSizeMetricName); + spawnLimiter.EntityAdded(entityId); + + if (!spawnLimiter.CanSpawnEntity()) + { + break; + } + } + } + + private void OnError(EntityId entityId, Exception ex) + { + GameObject context = null; + + var entityObject = universe.Get(entityId); + if (entityObject != null) + { + context = entityObject.UnderlyingGameObject; + } + + Debug.LogException(ex, context); + } + + /// + public IEntityPipelineBlock NextEntityBlock { get; set; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ThrottledEntityDispatcher.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ThrottledEntityDispatcher.cs.meta new file mode 100644 index 0000000..c1df3f4 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityAddons/LegacyEntityPipeline/ThrottledEntityDispatcher.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: c719d5029aded364596084d3b37520b7 +timeCreated: 1491560323 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityExtension.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityExtension.meta new file mode 100644 index 0000000..9111b85 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityExtension.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b8587db9bdd44a14bb4a858a09484500 +folderAsset: yes +timeCreated: 1444833351 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityExtension/GameObjectExtensions.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityExtension/GameObjectExtensions.cs new file mode 100644 index 0000000..039e308 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityExtension/GameObjectExtensions.cs @@ -0,0 +1,129 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + + +using System.Collections.Generic; +using Improbable; +using Improbable.Unity.Entity; + +// ReSharper disable once CheckNamespace +namespace UnityEngine +{ + /// + /// Helper methods for dealing with associations between Unity's GameObjects and SpatialOS entities. + /// + public static class GameObjectExtensions + { + private static readonly Dictionary GameObjectToEntityObjectCache = new Dictionary(); + private static readonly Dictionary> EntityObjectToGameObjectCache = new Dictionary>(); + + /// + /// Returns the SpatialOS entity ID associated with this GameObject. + /// + /// + /// Call or to check that there is + /// a SpatialOS entity associated with the GameObject. + /// This method searches the GameObject's parent hierarchy to find the root GameObject associated with a SpatialOS + /// entity. + /// Returns an invalid entity ID if neither the GameObject, nor any of its parents, are a SpatialOS entity. + /// + public static EntityId EntityId(this GameObject gameObject) + { + var entityObject = GetSpatialOsEntity(gameObject); + return entityObject == null ? new EntityId() : entityObject.EntityId; + } + + /// + /// Returns true if the specific GameObject is associated with a SpatialOS entity. + /// + public static bool IsSpatialOsEntity(this GameObject gameObject) + { + var storage = gameObject.GetComponent(); + return IsStorageValid(storage); + } + + /// + /// Returns true if the GameObject, or any of its parents, is associated with a SpatialOS entity. + /// + public static bool IsParentedBySpatialOsEntity(this GameObject gameObject) + { + var entity = GetSpatialOsEntity(gameObject); + return entity != null; + } + + /// + /// Finds the SpatialOS entity that belongs to the GameObject , or null if there is none associated. + /// + /// + /// This method searches the GameObject's parent hierarchy to find the root GameObject associated with a SpatialOS + /// entity. + /// The result is cached. + /// To clean the cache, call the method. + /// + public static IEntityObject GetSpatialOsEntity(this GameObject gameObject) + { + IEntityObject entityObject; + if (GameObjectToEntityObjectCache.TryGetValue(gameObject, out entityObject)) + { + return entityObject; + } + + var storage = gameObject.GetComponentInParent(); + if (!IsStorageValid(storage)) + { + return null; + } + + entityObject = storage.Entity; + + GameObjectToEntityObjectCache.Add(gameObject, entityObject); + AddToReverseLookup(gameObject, entityObject); + return entityObject; + } + + /// + /// Removes an entityObject from the lookup cache. + /// It will be re-added the next time it is looked up with . + /// + public static void RemoveFromLookupCache(IEntityObject entityObject) + { + if (entityObject == null) + { + return; + } + + List objectsToRemove; + if (!EntityObjectToGameObjectCache.TryGetValue(entityObject, out objectsToRemove)) + { + return; + } + + for (int index = 0; index < objectsToRemove.Count; index++) + { + var invalidObject = objectsToRemove[index]; + if (invalidObject != null) + { + GameObjectToEntityObjectCache.Remove(invalidObject); + } + } + + EntityObjectToGameObjectCache.Remove(entityObject); + } + + private static void AddToReverseLookup(GameObject gameObject, IEntityObject entityObject) + { + List gameObjects; + if (!EntityObjectToGameObjectCache.TryGetValue(entityObject, out gameObjects)) + { + gameObjects = new List(); + EntityObjectToGameObjectCache[entityObject] = gameObjects; + } + + gameObjects.Add(gameObject); + } + + private static bool IsStorageValid(EntityObjectStorage storage) + { + return storage != null && storage.Entity != null; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityExtension/GameObjectExtensions.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityExtension/GameObjectExtensions.cs.meta new file mode 100644 index 0000000..d11a5f8 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/UnityExtension/GameObjectExtensions.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: fa8dc9aeb66ce6c47a9b2f870ef6ff98 +timeCreated: 1444833363 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util.meta new file mode 100644 index 0000000..10e191c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: afb35d0ae5ba4a64eb6c9ab38fb54ef5 +folderAsset: yes +timeCreated: 1444833351 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CircularBuffer.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CircularBuffer.cs new file mode 100644 index 0000000..0cbd8d1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CircularBuffer.cs @@ -0,0 +1,77 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.Util +{ + /// + /// An array based circular buffer implementation. + /// + /// The type of objects stored in the buffer. + public class CircularBuffer + { + protected readonly T[] buffer; + protected readonly int size; + protected int index; + + /// + /// Create a circular buffer with the given size. + /// + public CircularBuffer(int size) + { + buffer = new T[size]; + this.size = size; + } + + /// + /// Add an item to the buffer. + /// + public void Add(T item) + { + buffer[index] = item; + index = (index + 1) % size; + } + + /// + /// Populates an array with the values in the buffer with the most recently added items first. + /// The given array must be of the same size as the buffer. + /// + public void GetItemsInMostRecentOrder(ref T[] array) + { + if (array.Length < size) + { + throw new ArgumentException("Given array must be of size >= the buffer size"); + } + + for (var i = 0; i < size; i++) + { + array[i] = buffer[(index - 1 - i + size) % size]; + } + } + } + + /// + /// A circular buffer to hold integers. + /// + public class CircularIntBuffer : CircularBuffer + { + /// + /// Creates a circular buffer to hold integers. + /// + public CircularIntBuffer(int size) : base(size) { } + + /// + /// Returns the average of the values in the circular buffer. + /// + public double GetAverage() + { + var sum = 0; + for (var i = 0; i < size; i++) + { + sum += buffer[i]; + } + + return (double) sum / size; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CircularBuffer.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CircularBuffer.cs.meta new file mode 100644 index 0000000..d27ebe9 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CircularBuffer.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 47150e26a5845ac44aab1135e8686949 +timeCreated: 1483370520 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CommandLineUtil.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CommandLineUtil.cs new file mode 100644 index 0000000..44f360e --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CommandLineUtil.cs @@ -0,0 +1,103 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; + +namespace Improbable.Unity.Util +{ + /// + /// Utilities to aid in parsing command line arguments and their default values. + /// + public static class CommandLineUtil + { + /// + /// Gets a value specified on the command line, in the form "+key" "value" + /// + /// The type of the value. + /// The arguments to inspect. + /// The name of the key, without the leading +, e.g. "key" + /// The value to return if the key was not specified on the command line. + /// The value of the key, or defaultValue if the key was not specified on the command line. + public static T GetCommandLineValue(IList arguments, string configKey, T defaultValue) + { + var dict = ParseCommandLineArgs(arguments); + T configValue; + if (TryGetConfigValue(dict, configKey, out configValue)) + { + return configValue; + } + + return defaultValue; + } + + /// + /// Tries to get a value specified on the command line, in the form "+key" "value" + /// + /// The type of the value. + /// The arguments to inspect. + /// The name of the key, without the leading +, e.g. "key" + /// The variable to store the result in, if found. + /// True if the key was found. + public static bool TryGetCommandLineValue(IList arguments, string configKey, out T configValue) + { + var dict = ParseCommandLineArgs(arguments); + return TryGetConfigValue(dict, configKey, out configValue); + } + + /// + /// Try to get am explicitly-typed value from the dictionary. + /// + /// True if the value was found, false otherwise. + public static bool TryGetConfigValue(Dictionary dictionary, string configName, out T value) + { + string strValue; + var desiredType = typeof(T); + if (dictionary.TryGetValue(configName, out strValue)) + { + if (desiredType.IsEnum) + { + try + { + value = (T) Enum.Parse(desiredType, strValue); + return true; + } + catch (Exception e) + { + throw new FormatException(string.Format("Unable to parse argument {0} as enum {1}.", strValue, desiredType.Name), e); + } + } + + value = (T) Convert.ChangeType(strValue, typeof(T)); + return true; + } + + value = default(T); + return false; + } + + /// + /// Parses a series of command line option pairs, beginning with '+' into a dictionary. + /// + /// + /// The arguments must be in the form: "+flag1 value1 +flag2 value2". + /// + public static Dictionary ParseCommandLineArgs(IList args) + { + var config = new Dictionary(); + for (var i = 0; i < args.Count; i++) + { + var flag = args[i]; + if (flag.StartsWith("+")) + { + var flagArg = args[i + 1]; + var strippedOfPlus = flag.Substring(1, flag.Length - 1); + config[strippedOfPlus] = flagArg; + // We've already processed the next argument, so skip it. + i++; + } + } + + return config; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CommandLineUtil.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CommandLineUtil.cs.meta new file mode 100644 index 0000000..d9d5e1b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/CommandLineUtil.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 752c9f2d0ca40be47a67ad5d832000d6 +timeCreated: 1484566447 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/IWWWRequest.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/IWWWRequest.cs new file mode 100644 index 0000000..99dece6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/IWWWRequest.cs @@ -0,0 +1,34 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + /// + /// Unit test wrapper for WWW requests. + /// + interface IWWWRequest + { + /// + /// Send a GET request. + /// + /// + /// Do not cache the WWWResponse callback argument and do not use the WWWResponse object outside of the callback + /// function. + /// + /// A request that can be cancelled. + WWWRequestWrapper SendGetRequest(MonoBehaviour coroutineHost, string url, Action callback); + + /// + /// Send a POST request. + /// + /// + /// Do not cache the WWWResponse callback argument and do not use the WWWResponse object outside of the callback + /// function. + /// + /// A request that can be cancelled. + WWWRequestWrapper SendPostRequest(MonoBehaviour coroutineHost, string url, byte[] postData, Dictionary headers, Action callback); + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/IWWWRequest.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/IWWWRequest.cs.meta new file mode 100644 index 0000000..8bd1897 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/IWWWRequest.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d99bf75a185b59843a1696f0c9703900 +timeCreated: 1498828651 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/Injection.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/Injection.meta new file mode 100644 index 0000000..c69e0b2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/Injection.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d192115f02e88e34fa8bacf38deae8ac +folderAsset: yes +timeCreated: 1491838525 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/Injection/IMemberAdapter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/Injection/IMemberAdapter.cs new file mode 100644 index 0000000..e34cf1b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/Injection/IMemberAdapter.cs @@ -0,0 +1,15 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Reflection; + +namespace Improbable.Util.Injection +{ + public interface IMemberAdapter + { + void SetValue(object obj, object value); + object GetValue(object obj); + Type TypeOfMember { get; } + MemberInfo Member { get; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/Injection/IMemberAdapter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/Injection/IMemberAdapter.cs.meta new file mode 100644 index 0000000..d946abf --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/Injection/IMemberAdapter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 10af65a3a4ee95d45b73bddbdb1a071c +timeCreated: 1469797235 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/MetricsUnityGui.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/MetricsUnityGui.cs new file mode 100644 index 0000000..e28f378 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/MetricsUnityGui.cs @@ -0,0 +1,68 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Globalization; +using System.Linq; +using Improbable.Unity.Core; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + public class MetricsUnityGui : MonoBehaviour + { + private Vector2 scrollPosition; + private bool showUnityMetrics; + private Texture2D backgroundTexture; + private GUIStyle backgroundStyle; + + private void Awake() + { + backgroundTexture = new Texture2D(1, 1); + backgroundTexture.SetPixel(0, 0, new Color(0, 0, 0, 0.7f)); + backgroundTexture.Apply(); + + backgroundStyle = new GUIStyle { normal = { background = backgroundTexture } }; + } + + private void OnDestroy() + { + Destroy(backgroundTexture); + } + + private void OnGUI() + { + GUILayout.BeginArea(new Rect(10, 50, Screen.width * 0.2f, Screen.height * 0.5f)); + GUILayout.BeginVertical(); + + scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, false); + + showUnityMetrics = GUILayout.Toggle(showUnityMetrics, "Metrics"); + + if (showUnityMetrics) + { + GUILayout.BeginVertical(backgroundStyle); + + var keys = SpatialOS.Metrics.Gauges.Keys.ToList(); + keys.Sort(); + + for (var i = 0; i < keys.Count; i++) + { + GUILayout.BeginHorizontal(); + + var key = keys[i]; + GUILayout.Label(key); + GUILayout.FlexibleSpace(); + GUILayout.Label(SpatialOS.Metrics.Gauges[key].ToString(CultureInfo.InvariantCulture)); + + GUILayout.EndHorizontal(); + } + + GUILayout.EndVertical(); + } + + GUILayout.EndScrollView(); + + GUILayout.EndVertical(); + GUILayout.EndArea(); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/MetricsUnityGui.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/MetricsUnityGui.cs.meta new file mode 100644 index 0000000..8c29861 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/MetricsUnityGui.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 0ef75f029f3d326418757222729ab076 +timeCreated: 1444833356 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/PathUtil.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/PathUtil.cs new file mode 100644 index 0000000..f48e8ce --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/PathUtil.cs @@ -0,0 +1,50 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.IO; +using System.Linq; + +namespace Improbable.Unity.Util +{ + public static class PathUtil + { + /// + /// Combines multiple path components via Path.Combine. + /// + public static string Combine(params string[] paths) + { + return paths.Aggregate(Path.Combine); + } + + /// + /// Attempts to create a directory if it does not already exist. + /// + public static void EnsureDirectoryExists(string directory) + { + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + } + + /// + /// Ensures a path is terminated with a Path.DirectorySeparatorChar . + /// + public static string EnsureTrailingSlash(string path) + { + if (string.IsNullOrEmpty(path) || path[path.Length - 1] == Path.DirectorySeparatorChar) + { + return path; + } + + return path + Path.DirectorySeparatorChar; + } + + /// + /// Converts the path in platform-native format to Unity path format "Foo/Bar". + /// + public static string ToUnityPath(this string nativePath) + { + return nativePath.Replace(Path.DirectorySeparatorChar, '/'); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/PathUtil.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/PathUtil.cs.meta new file mode 100644 index 0000000..1f9079c --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/PathUtil.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d08b88c7e596d9b4493615773b9a7d86 +timeCreated: 1469805904 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/TaskRunnerWithExponentialBackoff.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/TaskRunnerWithExponentialBackoff.cs new file mode 100644 index 0000000..5c9f487 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/TaskRunnerWithExponentialBackoff.cs @@ -0,0 +1,85 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + public class TaskRunnerWithExponentialBackoff + { + private bool isRunning = false; + private string identifier; + private MonoBehaviour corountineHost; + + private int remainingRetries; + private float timeUntilNextRetry; + + private Action RunTask; + private Func EvaluationFunc; + private Action OnSuccess; + private Action OnFailure; + + public void RunTaskWithRetries(string identifier, MonoBehaviour coroutineHost, Action runTask, Func evaluationFunc, Action onSuccess, Action onFailure, int remainingRetries = 3, float timeUntilNextRetry = 0.25f) + { + if (isRunning) + { + throw new InvalidOperationException("Task runner is already in use."); + } + + isRunning = true; + this.identifier = identifier; + this.corountineHost = coroutineHost; + this.remainingRetries = remainingRetries; + this.timeUntilNextRetry = timeUntilNextRetry; + RunTask = runTask; + EvaluationFunc = evaluationFunc; + OnSuccess = onSuccess; + OnFailure = onFailure; + + runTask(); + } + + public void ProcessResult(TResult response) + { + var result = EvaluationFunc(response); + if (result.IsSuccess) + { + isRunning = false; + OnSuccess(response); + } + else + { + if (--remainingRetries >= 0) + { + Debug.LogWarning(string.Format("Task [{0}] failed with message: {1}. Retrying with {2} retries left.", identifier, result.ErrorMessage, remainingRetries)); + Retry(); + } + else + { + isRunning = false; + Debug.LogError(string.Format("Task [{0}] failed with message: {1}. No retries left. Aborting.", identifier, result.ErrorMessage)); + OnFailure(result.ErrorMessage); + } + } + } + + private void Retry() + { + timeUntilNextRetry *= 2; + corountineHost.StartCoroutine(WaitAndPerform(timeUntilNextRetry, RunTask)); + } + + private IEnumerator WaitAndPerform(float delay, Action action) + { + yield return new WaitForSeconds(delay); + action(); + } + } + + public struct TaskResult + { + public bool IsSuccess; + public string ErrorMessage; + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/TaskRunnerWithExponentialBackoff.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/TaskRunnerWithExponentialBackoff.cs.meta new file mode 100644 index 0000000..9b68765 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/TaskRunnerWithExponentialBackoff.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 36200ac70c93efb499190e2bb30a98dd +timeCreated: 1498755006 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequest.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequest.cs new file mode 100644 index 0000000..b9c0285 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequest.cs @@ -0,0 +1,70 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + class WWWRequest : IWWWRequest + { + /// + public WWWRequestWrapper SendGetRequest(MonoBehaviour coroutineHost, string url, Action callback) + { + var requestWrapper = new WWWRequestWrapper(url); + + coroutineHost.StartCoroutine(SendRequestCoroutine(requestWrapper, callback)); + + return requestWrapper; + } + + /// + public WWWRequestWrapper SendPostRequest(MonoBehaviour coroutineHost, string url, byte[] postData, Dictionary headers, Action callback) + { + var requestWrapper = new WWWRequestWrapper(url, postData, headers); + + coroutineHost.StartCoroutine(SendRequestCoroutine(requestWrapper, callback)); + + return requestWrapper; + } + + private IEnumerator SendRequestCoroutine(WWWRequestWrapper requestWrapper, Action callback) + { + if (requestWrapper.IsCancelled) + { + yield break; + } + + using (var www = requestWrapper.CreateWww()) + { + bool isCancelled; + + while (true) + { + if (requestWrapper.IsCancelled) + { + isCancelled = true; + break; + } + + if (www.isDone) + { + isCancelled = false; + break; + } + + // Wait one frame + yield return null; + } + + if (!isCancelled) + { + var wwwResponse = WWWResponse.CreateFromWWW(www); + wwwResponse.Request = requestWrapper; + callback(wwwResponse); + } + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequest.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequest.cs.meta new file mode 100644 index 0000000..6689eb2 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequest.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: df2f7bb12c331e340b9596d57d3da5ad +timeCreated: 1498828651 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequestWrapper.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequestWrapper.cs new file mode 100644 index 0000000..c08a308 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequestWrapper.cs @@ -0,0 +1,49 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + class WWWRequestWrapper + { + public bool IsCancelled { get; private set; } + + private readonly string url; + + private readonly bool isPost; + private readonly byte[] postData; + private readonly Dictionary headers; + + public WWWRequestWrapper(string url) + { + this.url = url; + + isPost = false; + } + + public WWWRequestWrapper(string url, byte[] postData, Dictionary headers) + { + this.url = url; + this.postData = postData; + this.headers = headers; + + isPost = true; + } + + public WWW CreateWww() + { + if (isPost) + { + return new WWW(url, postData, headers); + } + + return new WWW(url); + } + + public void Cancel() + { + IsCancelled = true; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequestWrapper.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequestWrapper.cs.meta new file mode 100644 index 0000000..5128934 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWRequestWrapper.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 52c3474282e14fbebc11c5387a8d8347 +timeCreated: 1521826590 \ No newline at end of file diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWResponse.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWResponse.cs new file mode 100644 index 0000000..39ac388 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWResponse.cs @@ -0,0 +1,91 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + /// + /// Unit test wrapper for WWW response object. + /// + /// + /// Do not cache this object for later use as internal fields may be released spontaneously. + /// + class WWWResponse + { + private WWW www; + + private byte[] bytes; + + public byte[] Bytes + { + get { return bytes ?? (bytes = www.bytes); } + set { bytes = value; } + } + + private string error; + + public string Error + { + get { return error ?? (error = www.error); } + set { error = value; } + } + + private int bytesDownloaded = -1; + + public int BytesDownloaded + { + get + { + if (bytesDownloaded < 0) + { + bytesDownloaded = www.bytesDownloaded; + } + + return bytesDownloaded; + } + set { bytesDownloaded = value; } + } + + private string url; + + public string Url + { + get { return url ?? (url = www.url); } + set { url = value; } + } + + private string text; + + public string Text + { + get { return text ?? (text = www.text); } + set { text = value; } + } + + private AssetBundle assetBundle; + + public AssetBundle AssetBundle + { + get { return assetBundle ?? (assetBundle = www.assetBundle); } + set { assetBundle = value; } + } + + private Dictionary responseHeaders; + public WWWRequestWrapper Request; + + public Dictionary ResponseHeaders + { + get { return responseHeaders ?? (responseHeaders = www.responseHeaders); } + set { responseHeaders = value; } + } + + public static WWWResponse CreateFromWWW(WWW www) + { + return new WWWResponse + { + www = www + }; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWResponse.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWResponse.cs.meta new file mode 100644 index 0000000..3eb44db --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WWWResponse.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 80611ff385823f445b720239f0ab250c +timeCreated: 1507728057 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsFPS.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsFPS.cs new file mode 100644 index 0000000..24166c1 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsFPS.cs @@ -0,0 +1,81 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Diagnostics; +using Improbable.Unity.Core; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + public class WorkerMetricsFPS : MonoBehaviour + { + private const long UpdateMinPeriodMillis = 2000; // 1 update per 2 seconds. + private readonly Stopwatch stopwatch = new Stopwatch(); + private FPSMetric dynamicRate; + private FPSMetric fixedRate; + + public void Start() + { + dynamicRate = new FPSMetric("Dynamic"); + fixedRate = new FPSMetric("Fixed"); + stopwatch.Start(); + } + + public void FixedUpdate() + { + if (fixedRate != null) + { + fixedRate.FrameRendered(stopwatch.ElapsedMilliseconds); + } + } + + public void Update() + { + if (dynamicRate != null) + { + dynamicRate.FrameRendered(stopwatch.ElapsedMilliseconds); + } + } + + private class FPSMetric + { + private long startedAt; + private int frameCount; + private double dt; + private double fps; + + private readonly string fpsGaugeName; + private readonly string artGaugeName; + + internal FPSMetric(string prefix) + { + fpsGaugeName = prefix + ".FPS"; + artGaugeName = prefix + ".AverageRenderTime"; + } + + /* Time.deltaTime gives you the time to render the last frame. + * dt is then the sum of frame rendering time since last fps update. + * frameCount is the number of frame renderings since last fps update. + */ + internal void FrameRendered(long currentTime) + { + frameCount++; + dt += (1000 * Time.deltaTime); + var elapsed = currentTime - startedAt; + if (elapsed > UpdateMinPeriodMillis) + { + fps = ((frameCount * 1000.0) / elapsed); + WriteMetrics(); + frameCount = 0; + dt = 0; + startedAt = currentTime; + } + } + + private void WriteMetrics() + { + SpatialOS.Metrics.SetGauge(fpsGaugeName, fps); + SpatialOS.Metrics.SetGauge(artGaugeName, dt / frameCount); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsFPS.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsFPS.cs.meta new file mode 100644 index 0000000..784d37b --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsFPS.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: dc32d6f532f0501429cf470fee98e035 +timeCreated: 1444833362 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsMemoryUsage.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsMemoryUsage.cs new file mode 100644 index 0000000..f14ea66 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsMemoryUsage.cs @@ -0,0 +1,16 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using Improbable.Unity.Core; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + class WorkerMetricsMemoryUsage : MonoBehaviour + { + private void Update() + { + SpatialOS.Metrics.SetGauge("Unity used heap size", GC.GetTotalMemory( /* forceFullCollection */ false)); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsMemoryUsage.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsMemoryUsage.cs.meta new file mode 100644 index 0000000..ea43952 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerMetricsMemoryUsage.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d29444134e66ce54ab77e48c6b8e12a3 +timeCreated: 1444833362 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerTypeDisplay.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerTypeDisplay.cs new file mode 100644 index 0000000..2950664 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerTypeDisplay.cs @@ -0,0 +1,23 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using Improbable.Unity.Core; +using UnityEngine; + +namespace Improbable.Unity.Util +{ + public class WorkerTypeDisplay : MonoBehaviour + { + private static readonly Rect NAME_LABEL_POSITION = new Rect(10, 5, 300, 30); + private string workerType = string.Empty; + + private void Start() + { + workerType = WorkerTypeUtils.ToWorkerName(SpatialOS.Configuration.WorkerPlatform); + } + + private void OnGUI() + { + GUI.Label(NAME_LABEL_POSITION, workerType); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerTypeDisplay.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerTypeDisplay.cs.meta new file mode 100644 index 0000000..1c09dae --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Util/WorkerTypeDisplay.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8341e066cd112a24d9bb69ff8f6c39a9 +timeCreated: 1444833360 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer.meta new file mode 100644 index 0000000..8dda418 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 6faefc90764d66c4fb29af33552cbb46 +folderAsset: yes +timeCreated: 1444833351 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/AssemblyDependencyResolver.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/AssemblyDependencyResolver.cs new file mode 100644 index 0000000..227da50 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/AssemblyDependencyResolver.cs @@ -0,0 +1,69 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; +using System.Reflection; + +namespace Improbable.Unity.Visualizer +{ + class AssemblyDependencyResolver + { + /// + /// Given a rootAssembly and a list of all assemblies to consider, builds a dictionary that maps each assembly to + /// a boolean indicating whether or not it depends on the rootAssembly. + /// + internal static IDictionary GetAssemblyDependencyDictionary(Assembly rootAssembly, IList allAssemblies) + { + var dependenceDictionary = new Dictionary(); + var assemblyNameToAssemblyMap = CreateNameToAssemblyDictionary(allAssemblies); + dependenceDictionary.Add(rootAssembly.GetName().FullName, true); + for (int i = 0; i < allAssemblies.Count; ++i) + { + DependsOnRootAssembly(allAssemblies[i], dependenceDictionary, assemblyNameToAssemblyMap); + } + + return dependenceDictionary; + } + + private static IDictionary CreateNameToAssemblyDictionary(IList assemblies) + { + var assemblyNameToAssemblyMap = new Dictionary(); + for (int i = 0; i < assemblies.Count; ++i) + { + if (!assemblyNameToAssemblyMap.ContainsKey(assemblies[i].GetName().FullName)) + { + assemblyNameToAssemblyMap.Add(assemblies[i].GetName().FullName, assemblies[i]); + } + } + + return assemblyNameToAssemblyMap; + } + + private static bool DependsOnRootAssembly(Assembly assembly, IDictionary dependenceDictionary, IDictionary assemblyNameToAssembly) + { + if (!dependenceDictionary.ContainsKey(assembly.GetName().FullName)) + { + AnalyzeDependence(assembly, dependenceDictionary, assemblyNameToAssembly); + } + + return dependenceDictionary[assembly.GetName().FullName]; + } + + private static void AnalyzeDependence(Assembly assembly, IDictionary dependenceDictionary, IDictionary assemblyNameToAssembly) + { + var assemblyName = assembly.GetName().FullName; + dependenceDictionary.Add(assemblyName, false); + var assemblyDependencies = assembly.GetReferencedAssemblies(); + var dependsOn = false; + for (int i = 0; i < assemblyDependencies.Length; i++) + { + var assemblyDependencyName = assemblyDependencies[i].FullName; + if (assemblyNameToAssembly.ContainsKey(assemblyDependencyName) && assemblyDependencyName != assemblyName) + { + dependsOn |= DependsOnRootAssembly(assemblyNameToAssembly[assemblyDependencyName], dependenceDictionary, assemblyNameToAssembly); + } + } + + dependenceDictionary[assemblyName] = dependsOn; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/AssemblyDependencyResolver.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/AssemblyDependencyResolver.cs.meta new file mode 100644 index 0000000..8b0f9b5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/AssemblyDependencyResolver.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d6cae3ed65895e847a051c29a97b76d4 +timeCreated: 1462988721 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/DontAutoEnableAttribute.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/DontAutoEnableAttribute.cs new file mode 100644 index 0000000..38b8535 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/DontAutoEnableAttribute.cs @@ -0,0 +1,8 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.Visualizer +{ + public class DontAutoEnableAttribute : Attribute { } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/DontAutoEnableAttribute.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/DontAutoEnableAttribute.cs.meta new file mode 100644 index 0000000..590a669 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/DontAutoEnableAttribute.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 56937e92af7021b4d94b329fff26554e +timeCreated: 1445871866 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/FieldInfoAdapter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/FieldInfoAdapter.cs new file mode 100644 index 0000000..3a19c6a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/FieldInfoAdapter.cs @@ -0,0 +1,37 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Reflection; + +namespace Improbable.Util.Injection +{ + public class FieldInfoAdapter : IMemberAdapter + { + private readonly FieldInfo member; + + public MemberInfo Member + { + get { return member; } + } + + public FieldInfoAdapter(FieldInfo member) + { + this.member = member; + } + + public void SetValue(object obj, object value) + { + member.SetValue(obj, value); + } + + public object GetValue(object obj) + { + return member.GetValue(obj); + } + + public Type TypeOfMember + { + get { return member.FieldType; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/FieldInfoAdapter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/FieldInfoAdapter.cs.meta new file mode 100644 index 0000000..49e5e02 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/FieldInfoAdapter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ce32d533f2120b24f85634c515f2b22f +timeCreated: 1469797247 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/InjectionCache.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/InjectionCache.cs new file mode 100644 index 0000000..ffb2882 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/InjectionCache.cs @@ -0,0 +1,158 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; + +namespace Improbable.Util.Injection +{ + public class InjectionCache + { + // For each type, holds the list of members that contain at least one of the attributes passed. + private readonly IDictionary> injectableMembersCache = new Dictionary>(); + + // For each type, contains a mapping from an injectee type to a member that should be injected to. Null if the dictionary would have been empty. + private readonly IDictionary> memberInjectionCache = new Dictionary>(); + private readonly HashSet erroneousTypes = new HashSet(); + private readonly MemberReflectionUtil memberReflectionUtil; + + /// Attributes that mark injectable members. + public InjectionCache(params Type[] injectionAttributeTypes) + { + memberReflectionUtil = new MemberReflectionUtil(injectionAttributeTypes); + } + + public void RegisterType(Type classType) + { + if (classType == null || injectableMembersCache.ContainsKey(classType)) + { + return; + } + + RegisterType(classType.BaseType); + if (erroneousTypes.Contains(classType.BaseType)) + { + CacheUninjectableType(classType); + throw new ArgumentException("Type {0} cannot be registered as one of its base classes registered a failure.", classType.FullName); + } + + var membersToInject = memberReflectionUtil.GetMembersWithMatchingAttributes(classType); + ConcatLists(ref membersToInject, classType.BaseType == null ? null : injectableMembersCache[classType.BaseType]); + + var cache = CreateMemberInjectionCache(classType, membersToInject); + injectableMembersCache.Add(classType, membersToInject); + memberInjectionCache.Add(classType, cache); + } + + public void Inject(object targetOfInjection, Type typeOfInjectee, object injectee) + { + if (!TryInject(targetOfInjection, typeOfInjectee, injectee)) + { + throw new ArgumentException( + string.Format( + "Could not find a member in '{0}' to inject '{1}' (of type '{2}') into.", + targetOfInjection.GetType(), + injectee, + injectee != null ? injectee.GetType().ToString() : "null" + ) + ); + } + } + + public bool TryInject(object targetOfInjection, Type typeOfInjectee, object injectee) + { + var memberToInjectInto = GetAdapterForType(targetOfInjection.GetType(), typeOfInjectee); + if (memberToInjectInto != null) + { + memberToInjectInto.SetValue(targetOfInjection, injectee); + } + + return memberToInjectInto != null; + } + + public IMemberAdapter GetAdapterForType(Type targetType, Type typeOfInjectee) + { + RegisterType(targetType); + var cache = memberInjectionCache[targetType]; + IMemberAdapter memberToInjectInto = null; + if (cache == null || cache.TryGetValue(typeOfInjectee, out memberToInjectInto)) + { + return memberToInjectInto; + } + + var injectableMembers = injectableMembersCache[targetType]; + for (int i = 0; i < injectableMembers.Count; i++) + { + if (injectableMembers[i].TypeOfMember.IsAssignableFrom(typeOfInjectee)) + { + memberToInjectInto = injectableMembers[i]; + break; + } + } + + cache.Add(typeOfInjectee, memberToInjectInto); + return memberToInjectInto; + } + + public IList GetAdapters(object injectionTarget) + { + return GetAdaptersForType(injectionTarget.GetType()); + } + + public IList GetAdaptersForType(Type injectionTarget) + { + RegisterType(injectionTarget); + return injectableMembersCache[injectionTarget]; + } + + private void CacheUninjectableType(Type targetType) + { + injectableMembersCache.Add(targetType, null); + memberInjectionCache.Add(targetType, null); + erroneousTypes.Add(targetType); + } + + private IDictionary CreateMemberInjectionCache(Type classType, IList injectableMembers) + { + if (injectableMembers == null) + { + return null; + } + + var result = new Dictionary(); + for (int i = 0; i < injectableMembers.Count; ++i) + { + var typeToInject = injectableMembers[i].TypeOfMember; + if (!result.ContainsKey(typeToInject)) + { + result.Add(typeToInject, injectableMembers[i]); + } + else + { + CacheUninjectableType(classType); + throw new ArgumentException(string.Format("More than one member in '{0}' inject the type '{1}'.", classType, typeToInject)); + } + } + + return result; + } + + private static void ConcatLists(ref IList listA, IList listB) + { + if (listA == null) + { + listA = listB; + return; + } + + if (listB == null) + { + return; + } + + for (int i = 0; i < listB.Count; ++i) + { + listA.Add(listB[i]); + } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/InjectionCache.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/InjectionCache.cs.meta new file mode 100644 index 0000000..bb17e63 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/InjectionCache.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 5c1e907d2620f8b4ca8800b7c375ccbe +timeCreated: 1469797243 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/MemberReflectionUtil.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/MemberReflectionUtil.cs new file mode 100644 index 0000000..deb654a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/MemberReflectionUtil.cs @@ -0,0 +1,74 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Improbable.Util.Injection +{ + class MemberReflectionUtil + { + private readonly Type[] attributeTypes; + + private const BindingFlags FieldFlags = BindingFlags.Instance | BindingFlags.NonPublic | + BindingFlags.Public | BindingFlags.DeclaredOnly; + + private const BindingFlags PropertyFlags = + FieldFlags | BindingFlags.SetProperty | BindingFlags.GetProperty; + + internal MemberReflectionUtil(Type[] attributeTypes) + { + this.attributeTypes = attributeTypes; + } + + /// + /// A MemberAdapter list of properties and fields declared in given type that match at least one of given attributes. + /// + internal IList GetMembersWithMatchingAttributes(Type targetType) + { + IList result = null; + ProcessMembers(ref result, targetType.GetProperties(PropertyFlags), CreatePropertyAdapter); + ProcessMembers(ref result, targetType.GetFields(FieldFlags), CreateFieldAdapter); + return result; + } + + private void ProcessMembers(ref IList resultList, IList members, Func adapterFactory) where T : MemberInfo + { + for (int i = 0; i < members.Count; i++) + { + if (MemberHasAttribute(members[i])) + { + if (resultList == null) + { + resultList = new List(); + } + + resultList.Add(adapterFactory(members[i])); + } + } + } + + private bool MemberHasAttribute(MemberInfo member) + { + for (int i = 0; i < attributeTypes.Length; i++) + { + if (Attribute.IsDefined(member, attributeTypes[i], false)) + { + return true; + } + } + + return false; + } + + private static FieldInfoAdapter CreateFieldAdapter(FieldInfo member) + { + return new FieldInfoAdapter(member); + } + + private static PropertyInfoAdapter CreatePropertyAdapter(PropertyInfo member) + { + return new PropertyInfoAdapter(member); + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/MemberReflectionUtil.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/MemberReflectionUtil.cs.meta new file mode 100644 index 0000000..9184c20 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/MemberReflectionUtil.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 2eeecbd54ecc1cc4fb33bddb89ece035 +timeCreated: 1469797240 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/PropertyInfoAdapter.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/PropertyInfoAdapter.cs new file mode 100644 index 0000000..7099651 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/PropertyInfoAdapter.cs @@ -0,0 +1,37 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Reflection; + +namespace Improbable.Util.Injection +{ + public class PropertyInfoAdapter : IMemberAdapter + { + private readonly PropertyInfo member; + + public MemberInfo Member + { + get { return member; } + } + + public PropertyInfoAdapter(PropertyInfo member) + { + this.member = member; + } + + public void SetValue(object obj, object value) + { + member.SetValue(obj, value, null); + } + + public object GetValue(object obj) + { + return member.GetValue(obj, null); + } + + public Type TypeOfMember + { + get { return member.PropertyType; } + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/PropertyInfoAdapter.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/PropertyInfoAdapter.cs.meta new file mode 100644 index 0000000..cca2b11 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/PropertyInfoAdapter.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: eb11e687feb94f140bb5e48ff76f4ef0 +timeCreated: 1469797248 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/RequireAttribute.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/RequireAttribute.cs new file mode 100644 index 0000000..6c58675 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/RequireAttribute.cs @@ -0,0 +1,9 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.Visualizer +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class RequireAttribute : Attribute { } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/RequireAttribute.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/RequireAttribute.cs.meta new file mode 100644 index 0000000..96db137 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/RequireAttribute.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 60ca7a71f9ee5b64282cb8c9cbcabe76 +timeCreated: 1444833358 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/VisualizerMetadataLookup.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/VisualizerMetadataLookup.cs new file mode 100644 index 0000000..3d8f9f6 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/VisualizerMetadataLookup.cs @@ -0,0 +1,278 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; +using System.Collections.Generic; +using Improbable.Entity.Component; +using Improbable.Util.Injection; +using UnityEngine; + +namespace Improbable.Unity.Visualizer +{ + sealed class VisualizerMetadataLookup + { + private static readonly VisualizerMetadataLookup ObjectInstance = new VisualizerMetadataLookup(); + + private readonly InjectionCache visualizerInjectionCache = new InjectionCache(typeof(RequireAttribute)); + private readonly HashSet visualizers = new HashSet(); + private readonly HashSet readers = new HashSet(); + private readonly HashSet writers = new HashSet(); + private readonly Dictionary> visualizerRequiredReadersWriters = new Dictionary>(); + private readonly Dictionary> visualizerRequiredWriters = new Dictionary>(); + private readonly Dictionary> visualizerRequiredReaderStateIds = new Dictionary>(); + private readonly HashSet visualizersToNotAutoEnableOnStart = new HashSet(); + private readonly HashSet visualizersWithNonInjectableRequiredFields = new HashSet(); + private HashSet requiredTypesSeen; + + public static VisualizerMetadataLookup Instance + { + get { return ObjectInstance; } + } + + private VisualizerMetadataLookup() + { + InitializeMetadata(); + } + + public IEnumerable AllRequiredTypes + { + get { return requiredTypesSeen; } + } + + public bool IsVisualizer(Type visualizerType) + { + return visualizers.Contains(visualizerType); + } + + public IList GetRequiredWriters(Type visualizerType) + { + return visualizerRequiredWriters[visualizerType]; + } + + public bool AreAllRequiredFieldsInjectable(Type visualizer) + { + return !visualizersWithNonInjectableRequiredFields.Contains(visualizer); + } + + public IList GetRequiredReaderComponentIds(Type visualizerType) + { + return visualizerRequiredReaderStateIds[visualizerType]; + } + + public bool IsWriter(IMemberAdapter fieldInfo) + { + return writers.Contains(fieldInfo.TypeOfMember); + } + + public bool IsReader(IMemberAdapter fieldInfo) + { + return readers.Contains(fieldInfo.TypeOfMember); + } + + public IList GetRequiredReadersWriters(Type visualizerType) + { + return visualizerRequiredReadersWriters[visualizerType]; + } + + public bool DontEnableOnStart(Type visualizerType) + { + return visualizersToNotAutoEnableOnStart.Contains(visualizerType); + } + + public IMemberAdapter GetFieldInfo(Type stateType, Type visualizerType) + { + return visualizerInjectionCache.GetAdapterForType(visualizerType, stateType); + } + + private void InitializeMetadata() + { + var types = GetAssemblyTypes(); + requiredTypesSeen = new HashSet(); + for (int i = 0; i < types.Count; ++i) + { + InitializeMetadataForType(requiredTypesSeen, types[i]); + } + } + + private void InitializeMetadataForType(HashSet requiredTypesSeen, Type type) + { + if (IsVisualizerInternal(type)) + { + visualizers.Add(type); + if (Attribute.IsDefined(type, typeof(DontAutoEnableAttribute), true)) + { + visualizersToNotAutoEnableOnStart.Add(type); + } + + InitializeMetadataForMembers(requiredTypesSeen, type, visualizerInjectionCache.GetAdaptersForType(type)); + } + else + { + CheckForVisualizerOnlyAttributes(type); + } + } + + private void InitializeMetadataForMembers(HashSet requiredTypesSeen, Type type, IList requiredMembers) + { + var maxCount = requiredMembers == null ? 0 : requiredMembers.Count; + List requiredReadersWriters = new List(maxCount); + List requiredWriters = new List(maxCount); + var readersStateIds = new List(maxCount); + + visualizerRequiredReadersWriters.Add(type, requiredReadersWriters); + visualizerRequiredWriters.Add(type, requiredWriters); + visualizerRequiredReaderStateIds.Add(type, readersStateIds); + + if (requiredMembers == null) + { + return; + } + + for (int j = 0; j < requiredMembers.Count; ++j) + { + var requiredMember = requiredMembers[j]; + InitializeMetadataForMember(requiredTypesSeen, type, requiredMember, requiredReadersWriters, requiredWriters, readersStateIds); + } + } + + private void InitializeMetadataForMember(HashSet requiredTypesSeen, Type type, IMemberAdapter requiredMember, List requiredReadersWriters, List requiredWriters, List readersStateIds) + { + CacheReadersAndWriters(requiredTypesSeen, requiredMember); + + if (IsWriter(requiredMember)) + { + requiredReadersWriters.Add(requiredMember); + requiredWriters.Add(requiredMember); + } + else if (IsReader(requiredMember)) + { + requiredReadersWriters.Add(requiredMember); + var canonicalNameAttribute = Attribute.GetCustomAttribute(requiredMember.TypeOfMember, typeof(ComponentIdAttribute), false); + if (canonicalNameAttribute != null) + { + readersStateIds.Add(((ComponentIdAttribute) canonicalNameAttribute).Id); + } + else + { + Debug.LogErrorFormat("Could not find state metadata for Reader {0}. This might cause issues with the state not being synchronised to the worker.", requiredMember.TypeOfMember.FullName); + } + } + else + { + visualizersWithNonInjectableRequiredFields.Add(type); + Debug.LogErrorFormat("The [Require] attribute can only be used on state Readers and Writers. {0} {1} is not one of those in visualizer {2}. The visualizer won't be enabled.", + requiredMember.TypeOfMember.FullName, requiredMember.Member.Name, type.FullName); + } + } + + private void CacheReadersAndWriters(HashSet requiredTypesSeen, IMemberAdapter requiredMember) + { + if (requiredTypesSeen.Contains(requiredMember.TypeOfMember)) + { + return; + } + + requiredTypesSeen.Add(requiredMember.TypeOfMember); + if (Attribute.IsDefined(requiredMember.TypeOfMember, typeof(WriterInterfaceAttribute), false)) + { + writers.Add(requiredMember.TypeOfMember); + } + + if (Attribute.IsDefined(requiredMember.TypeOfMember, typeof(ReaderInterfaceAttribute), false)) + { + readers.Add(requiredMember.TypeOfMember); + } + } + + private void CheckForVisualizerOnlyAttributes(Type type) + { + if (Attribute.IsDefined(type, typeof(DontAutoEnableAttribute), false)) + { + Debug.LogWarningFormat("{0} uses DontAutoEnableAttribute but is not a managed behaviour as it has no [Require] fields. The attribute will be ignored.", type.FullName); + } + } + + private IList GetAssemblyTypes() + { + var allAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + var assembliesPotentiallyUsingRequire = + AssemblyDependencyResolver.GetAssemblyDependencyDictionary(typeof(RequireAttribute).Assembly, allAssemblies); + var types = new List(); + for (int i = 0; i < allAssemblies.Length; ++i) + { + if (!assembliesPotentiallyUsingRequire[allAssemblies[i].GetName().FullName]) + { + continue; + } + + var assemblyTypes = allAssemblies[i].GetTypes(); + for (int j = 0; j < assemblyTypes.Length; ++j) + { + types.Add(assemblyTypes[j]); + } + } + + return types; + } + + private bool IsVisualizerInternal(Type visualizerType) + { + if (visualizerType.IsAbstract || visualizerType.IsInterface) + { + return false; + } + + var registered = TryRegisterVisualizer(visualizerType); + return + !registered //If registering failed, must have been a visualizer + || visualizerInjectionCache.GetAdaptersForType(visualizerType) != null; // Any type with either worker or Required, or Data attributes + } + + private bool TryRegisterVisualizer(Type visualizer) + { + try + { + visualizerInjectionCache.RegisterType(visualizer); + return true; + } + catch (ArgumentException e) + { + Debug.LogErrorFormat(e.Message); + visualizersWithNonInjectableRequiredFields.Add(visualizer); + return false; + } + } + + public static IList ExtractVisualizers(GameObject gameObject) + { + var foundVisualizers = new List(); + if (gameObject != null) + { + var componentsInChildren = gameObject.GetComponentsInChildren(); + if (componentsInChildren == null) + { + Debug.LogErrorFormat("GetComponentsInChildren returned null for GameObject: {0}", gameObject.name); + } + else + { + for (int index = 0; index < componentsInChildren.Length; index++) + { + var visualizer = componentsInChildren[index]; + if (visualizer == null) + { + Debug.LogErrorFormat("GetComponentsInChildren returned a null element for GameObject {0}", gameObject.name); + continue; + } + + var visualizerType = visualizer.GetType(); + if (Instance.IsVisualizer(visualizerType)) + { + foundVisualizers.Add(visualizer); + } + } + } + } + + return foundVisualizers; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/VisualizerMetadataLookup.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/VisualizerMetadataLookup.cs.meta new file mode 100644 index 0000000..6607744 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/VisualizerMetadataLookup.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f72499528faa7604e84f3b3ec6592d7a +timeCreated: 1444833363 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/WorkerTypeAttribute.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/WorkerTypeAttribute.cs new file mode 100644 index 0000000..68ba917 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/WorkerTypeAttribute.cs @@ -0,0 +1,17 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity.Visualizer +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + public class WorkerTypeAttribute : Attribute + { + public WorkerTypeAttribute(WorkerPlatform workerPlatform) + { + WorkerPlatform = workerPlatform; + } + + public WorkerPlatform WorkerPlatform { get; private set; } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/WorkerTypeAttribute.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/WorkerTypeAttribute.cs.meta new file mode 100644 index 0000000..492b71a --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/Visualizer/WorkerTypeAttribute.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: b0e18918b3e2bdf4981c3e56b0d94ef2 +timeCreated: 1444833361 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerPlatform.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerPlatform.cs new file mode 100644 index 0000000..0e44de5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerPlatform.cs @@ -0,0 +1,13 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System; + +namespace Improbable.Unity +{ + [Flags] + public enum WorkerPlatform + { + UnityWorker = 0x1, + UnityClient = 0x2, + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerPlatform.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerPlatform.cs.meta new file mode 100644 index 0000000..b1892b5 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerPlatform.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 20a1968e1c88aeb40a07ad50c3b55efd +timeCreated: 1484563616 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerTypeUtils.cs b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerTypeUtils.cs new file mode 100644 index 0000000..0180b9d --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerTypeUtils.cs @@ -0,0 +1,34 @@ +// Copyright (c) Improbable Worlds Ltd, All Rights Reserved + +using System.Collections.Generic; + +namespace Improbable.Unity +{ + public static class WorkerTypeUtils + { + public const string UnityClientType = "UnityClient"; + public const string UnityWorkerType = "UnityWorker"; + + private static readonly Dictionary WorkerPlatformToNameMap = new Dictionary + { + { WorkerPlatform.UnityClient, UnityClientType }, + { WorkerPlatform.UnityWorker, UnityWorkerType } + }; + + private static readonly Dictionary NameToWorkerPlatformMap = new Dictionary + { + { UnityClientType, WorkerPlatform.UnityClient }, + { UnityWorkerType, WorkerPlatform.UnityWorker } + }; + + public static string ToWorkerName(WorkerPlatform workerPlatform) + { + return WorkerPlatformToNameMap[workerPlatform]; + } + + public static WorkerPlatform FromWorkerName(string workerName) + { + return NameToWorkerPlatformMap[workerName]; + } + } +} diff --git a/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerTypeUtils.cs.meta b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerTypeUtils.cs.meta new file mode 100644 index 0000000..34f7c41 --- /dev/null +++ b/workers/unity/Assets/Plugins/Improbable/Sdk/Src/Unity/WorkerTypeUtils.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 225adb2aa747cb94baa2f422ac3946af +timeCreated: 1484563616 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/workers/unity/Improbable/bin/CodeGenerator.dll b/workers/unity/Improbable/bin/CodeGenerator.dll new file mode 100644 index 0000000..fab050b Binary files /dev/null and b/workers/unity/Improbable/bin/CodeGenerator.dll differ diff --git a/workers/unity/Improbable/bin/Improbable.TextTemplating.dll b/workers/unity/Improbable/bin/Improbable.TextTemplating.dll new file mode 100644 index 0000000..dc5cc39 Binary files /dev/null and b/workers/unity/Improbable/bin/Improbable.TextTemplating.dll differ diff --git a/workers/unity/Improbable/bin/Mono.TextTemplating.dll b/workers/unity/Improbable/bin/Mono.TextTemplating.dll new file mode 100644 index 0000000..bf879b7 Binary files /dev/null and b/workers/unity/Improbable/bin/Mono.TextTemplating.dll differ diff --git a/workers/unity/Improbable/bin/Newtonsoft.Json.dll b/workers/unity/Improbable/bin/Newtonsoft.Json.dll new file mode 100644 index 0000000..78a1763 Binary files /dev/null and b/workers/unity/Improbable/bin/Newtonsoft.Json.dll differ diff --git a/workers/unity/Improbable/bin/UnityCodeGenerator.exe b/workers/unity/Improbable/bin/UnityCodeGenerator.exe new file mode 100644 index 0000000..838ded6 Binary files /dev/null and b/workers/unity/Improbable/bin/UnityCodeGenerator.exe differ diff --git a/workers/unity/Improbable/bin/macos/clean b/workers/unity/Improbable/bin/macos/clean new file mode 100644 index 0000000..b4b8b33 Binary files /dev/null and b/workers/unity/Improbable/bin/macos/clean differ diff --git a/workers/unity/Improbable/bin/macos/unity-csharp-invoker b/workers/unity/Improbable/bin/macos/unity-csharp-invoker new file mode 100644 index 0000000..0f8c346 Binary files /dev/null and b/workers/unity/Improbable/bin/macos/unity-csharp-invoker differ diff --git a/workers/unity/Improbable/bin/macos/unity-invoker b/workers/unity/Improbable/bin/macos/unity-invoker new file mode 100644 index 0000000..20d28ad Binary files /dev/null and b/workers/unity/Improbable/bin/macos/unity-invoker differ diff --git a/workers/unity/Improbable/bin/macos/unity-mono-invoker b/workers/unity/Improbable/bin/macos/unity-mono-invoker new file mode 100644 index 0000000..80afb71 Binary files /dev/null and b/workers/unity/Improbable/bin/macos/unity-mono-invoker differ diff --git a/workers/unity/Improbable/bin/macos/unity-zip b/workers/unity/Improbable/bin/macos/unity-zip new file mode 100644 index 0000000..2d17b40 Binary files /dev/null and b/workers/unity/Improbable/bin/macos/unity-zip differ diff --git a/workers/unity/Improbable/bin/windows/clean.exe b/workers/unity/Improbable/bin/windows/clean.exe new file mode 100644 index 0000000..76a4b3a Binary files /dev/null and b/workers/unity/Improbable/bin/windows/clean.exe differ diff --git a/workers/unity/Improbable/bin/windows/unity-csharp-invoker.exe b/workers/unity/Improbable/bin/windows/unity-csharp-invoker.exe new file mode 100644 index 0000000..c167945 Binary files /dev/null and b/workers/unity/Improbable/bin/windows/unity-csharp-invoker.exe differ diff --git a/workers/unity/Improbable/bin/windows/unity-invoker.exe b/workers/unity/Improbable/bin/windows/unity-invoker.exe new file mode 100644 index 0000000..4519b0f Binary files /dev/null and b/workers/unity/Improbable/bin/windows/unity-invoker.exe differ diff --git a/workers/unity/Improbable/bin/windows/unity-mono-invoker.exe b/workers/unity/Improbable/bin/windows/unity-mono-invoker.exe new file mode 100644 index 0000000..bbc6a03 Binary files /dev/null and b/workers/unity/Improbable/bin/windows/unity-mono-invoker.exe differ diff --git a/workers/unity/Improbable/bin/windows/unity-zip.exe b/workers/unity/Improbable/bin/windows/unity-zip.exe new file mode 100644 index 0000000..b0a1656 Binary files /dev/null and b/workers/unity/Improbable/bin/windows/unity-zip.exe differ diff --git a/workers/unity/Improbable/build_script/spatialos.unity.client.build.experimental.json b/workers/unity/Improbable/build_script/spatialos.unity.client.build.experimental.json new file mode 100644 index 0000000..f92542c --- /dev/null +++ b/workers/unity/Improbable/build_script/spatialos.unity.client.build.experimental.json @@ -0,0 +1,140 @@ +{ + "tasks": [ + { + "name": "Codegen", + "steps": [ + { + "name": "C# standard library", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_std", + "--output=.spatialos/generated/std", + "--language=csharp", + "--input=../../build/dependencies/schema/standard_library", + "--version=13.0.0" + ] + }, + { + "name": "C#", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_usr", + "--output=.spatialos/generated/usr", + "--language=csharp", + "--input=../../schema", + "--repository=../../build/dependencies/schema", + "--version=13.0.0" + ] + }, + { + "name": "Json AST standard library", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_json_std", + "--output=.spatialos/json/std", + "--language=ast_json", + "--input=../../build/dependencies/schema/standard_library", + "--version=13.0.0" + ] + }, + { + "name": "Json AST", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_json_usr", + "--output=.spatialos/json/usr", + "--language=ast_json", + "--input=../../schema", + "--repository=../../build/dependencies/schema", + "--version=13.0.0" + ] + }, + { + "name": "Unity Code Generation", + "description": "Generates Unity MonoBehaviours, readers and writers from the project schema.", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-mono-invoker", + "arguments": [ + "Improbable/bin/UnityCodeGenerator.exe", + "--", + "--json-dir=.spatialos/json", + "--unity-component-output-dir=Assets/Plugins/Improbable/Generated/Components", + "--unity-editor-component-output-dir=Assets/Plugins/Improbable/Editor/Generated/Components", + "--reader-writer-output-dir=.spatialos/generated/readers_writers" + ] + }, + { + "name": "Compile generated scripts", + "description": "Compiles the generated code into a single DLL for Unity to consume.", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-csharp-invoker", + "arguments": [ + "--unity-reference=UnityEngine.dll", + "--", + "-lib:Assets/Plugins/Improbable/Sdk/Dll", + "-reference:Improbable.WorkerSdkCsharp.dll,Improbable.WorkerSdkCsharp.Framework.dll", + "-target:library", + "-debug", + "-unsafe", + "-nowarn:1591", + "-recurse:.spatialos/generated/*.cs", + "-recurse:Assets/Plugins/Improbable/.UnityPartials/*.cs", + "-out:Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.dll", + "-doc:Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.xml" + ] + } + ] + }, + { + "name": "Build", + "steps": [ + { + "name": "Codegen", + "arguments": [ + "invoke-task", + "Codegen" + ] + }, + { + "name": "UnityClient workers", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-invoker", + "arguments": [ + "--", + "-batchmode", + "-quit", + "-nographics", + "-logfile", + "../../logs/${IMPROBABLE_WORKER_NAME}/unity_experimental_invoke.log", + "-projectPath", + "${IMPROBABLE_WORKER_DIR}", + "Improbable.Unity.MinimalBuildSystem.WorkerBuilder.Build", + "+buildWorkerTypes", + "${IMPROBABLE_WORKER_NAME}", + "+buildTarget", + "${IMPROBABLE_BUILD_TARGET}" + ] + } + ] + }, + { + "name": "Clean", + "steps": [ + { + "name": "Build artifacts and generated code", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/clean", + "arguments": [ + ".spatialos", + "Assets/Plugins/Improbable/Generated", + "Assets/Plugins/Improbable/Editor/Generated/Components", + "Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.dll", + "Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.xml", + "../../build/assembly/unity", + "../../build/assembly/${IMPROBABLE_WORKER_NAME}" + ] + } + ] + } + ] +} diff --git a/workers/unity/Improbable/build_script/spatialos.unity.client.build.json b/workers/unity/Improbable/build_script/spatialos.unity.client.build.json new file mode 100644 index 0000000..c1d7c16 --- /dev/null +++ b/workers/unity/Improbable/build_script/spatialos.unity.client.build.json @@ -0,0 +1,139 @@ +{ + "tasks": [ + { + "name": "Codegen", + "steps": [ + { + "name": "C# standard library", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_std", + "--output=.spatialos/generated/std", + "--language=csharp", + "--input=../../build/dependencies/schema/standard_library", + "--version=13.0.0" + ] + }, + { + "name": "C#", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_usr", + "--output=.spatialos/generated/usr", + "--language=csharp", + "--input=../../schema", + "--repository=../../build/dependencies/schema", + "--version=13.0.0" + ] + }, + { + "name": "Json AST standard library", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_json_std", + "--output=.spatialos/json/std", + "--language=ast_json", + "--input=../../build/dependencies/schema/standard_library", + "--version=13.0.0" + ] + }, + { + "name": "Json AST", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_json_usr", + "--output=.spatialos/json/usr", + "--language=ast_json", + "--input=../../schema", + "--repository=../../build/dependencies/schema", + "--version=13.0.0" + ] + }, + { + "name": "Unity Code Generation", + "description": "Generates Unity MonoBehaviours, readers and writers from the project schema.", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-mono-invoker", + "arguments": [ + "Improbable/bin/UnityCodeGenerator.exe", + "--", + "--json-dir=.spatialos/json", + "--unity-component-output-dir=Assets/Plugins/Improbable/Generated/Components", + "--unity-editor-component-output-dir=Assets/Plugins/Improbable/Editor/Generated/Components", + "--reader-writer-output-dir=.spatialos/generated/readers_writers" + ] + }, + { + "name": "Compile generated scripts", + "description": "Compiles the generated code into a single DLL for Unity to consume.", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-csharp-invoker", + "arguments": [ + "--unity-reference=UnityEngine.dll", + "--", + "-lib:Assets/Plugins/Improbable/Sdk/Dll", + "-reference:Improbable.WorkerSdkCsharp.dll,Improbable.WorkerSdkCsharp.Framework.dll", + "-target:library", + "-debug", + "-unsafe", + "-nowarn:1591", + "-recurse:.spatialos/generated/*.cs", + "-recurse:Assets/Plugins/Improbable/.UnityPartials/*.cs", + "-out:Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.dll", + "-doc:Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.xml" + ] + } + ] + }, + { + "name": "Build", + "steps": [ + { + "name": "Codegen", + "arguments": [ + "invoke-task", + "Codegen" + ] + }, + { + "name": "UnityClient workers", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-invoker", + "arguments": [ + "--", + "-batchmode", + "-quit", + "-nographics", + "-logfile", + "../../logs/${IMPROBABLE_WORKER_NAME}/unity_invoke.log", + "-projectPath", + "${IMPROBABLE_WORKER_DIR}", + "-executeMethod", + "Improbable.Unity.EditorTools.Build.SimpleBuildSystem.Build", + "+buildWorkerTypes", + "${IMPROBABLE_WORKER_NAME}" + ] + } + ] + }, + { + "name": "Clean", + "steps": [ + { + "name": "Build artifacts and generated code", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/clean", + "arguments": [ + ".spatialos", + "Assets/Plugins/Improbable/Generated", + "Assets/Plugins/Improbable/Editor/Generated/Components", + "Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.dll", + "Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.xml", + "../../build/assembly/unity", + "../../build/assembly/${IMPROBABLE_WORKER_NAME}" + ] + } + ] + } + ] +} diff --git a/workers/unity/Improbable/build_script/spatialos.unity.worker.build.experimental.json b/workers/unity/Improbable/build_script/spatialos.unity.worker.build.experimental.json new file mode 100644 index 0000000..d48ea30 --- /dev/null +++ b/workers/unity/Improbable/build_script/spatialos.unity.worker.build.experimental.json @@ -0,0 +1,140 @@ +{ + "tasks": [ + { + "name": "Codegen", + "steps": [ + { + "name": "C# standard library", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_std", + "--output=.spatialos/generated/std", + "--language=csharp", + "--input=../../build/dependencies/schema/standard_library", + "--version=13.0.0" + ] + }, + { + "name": "C#", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_usr", + "--output=.spatialos/generated/usr", + "--language=csharp", + "--input=../../schema", + "--repository=../../build/dependencies/schema", + "--version=13.0.0" + ] + }, + { + "name": "Json AST standard library", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_json_std", + "--output=.spatialos/json/std", + "--language=ast_json", + "--input=../../build/dependencies/schema/standard_library", + "--version=13.0.0" + ] + }, + { + "name": "Json AST", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_json_usr", + "--output=.spatialos/json/usr", + "--language=ast_json", + "--input=../../schema", + "--repository=../../build/dependencies/schema", + "--version=13.0.0" + ] + }, + { + "name": "Unity Code Generation", + "description": "Generates Unity MonoBehaviours, readers and writers from the project schema.", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-mono-invoker", + "arguments": [ + "Improbable/bin/UnityCodeGenerator.exe", + "--", + "--json-dir=.spatialos/json", + "--unity-component-output-dir=Assets/Plugins/Improbable/Generated/Components", + "--unity-editor-component-output-dir=Assets/Plugins/Improbable/Editor/Generated/Components", + "--reader-writer-output-dir=.spatialos/generated/readers_writers" + ] + }, + { + "name": "Compile generated scripts", + "description": "Compiles the generated code into a single DLL for Unity to consume.", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-csharp-invoker", + "arguments": [ + "--unity-reference=UnityEngine.dll", + "--", + "-lib:Assets/Plugins/Improbable/Sdk/Dll", + "-reference:Improbable.WorkerSdkCsharp.dll,Improbable.WorkerSdkCsharp.Framework.dll", + "-target:library", + "-debug", + "-unsafe", + "-nowarn:1591", + "-recurse:.spatialos/generated/*.cs", + "-recurse:Assets/Plugins/Improbable/.UnityPartials/*.cs", + "-out:Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.dll", + "-doc:Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.xml" + ] + } + ] + }, + { + "name": "Build", + "steps": [ + { + "name": "Codegen", + "arguments": [ + "invoke-task", + "Codegen" + ] + }, + { + "name": "UnityWorker workers", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-invoker", + "arguments": [ + "--", + "-batchmode", + "-quit", + "-nographics", + "-logfile", + "../../logs/${IMPROBABLE_WORKER_NAME}/unity_experimental_invoke.log", + "-projectPath", + "${IMPROBABLE_WORKER_DIR}", + "Improbable.Unity.MinimalBuildSystem.WorkerBuilder.Build", + "+buildWorkerTypes", + "${IMPROBABLE_WORKER_NAME}", + "+buildTarget", + "${IMPROBABLE_BUILD_TARGET}" + ] + } + ] + }, + { + "name": "Clean", + "steps": [ + { + "name": "Build artifacts and generated code", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/clean", + "arguments": [ + ".spatialos", + "Assets/Plugins/Improbable/Generated", + "Assets/Plugins/Improbable/Editor/Generated/Components", + "Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.dll", + "Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.xml", + "../../build/assembly/unity", + "../../build/assembly/${IMPROBABLE_WORKER_NAME}" + ] + } + ] + } + ] +} diff --git a/workers/unity/Improbable/build_script/spatialos.unity.worker.build.json b/workers/unity/Improbable/build_script/spatialos.unity.worker.build.json new file mode 100644 index 0000000..2366328 --- /dev/null +++ b/workers/unity/Improbable/build_script/spatialos.unity.worker.build.json @@ -0,0 +1,139 @@ +{ + "tasks": [ + { + "name": "Codegen", + "steps": [ + { + "name": "C# standard library", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_std", + "--output=.spatialos/generated/std", + "--language=csharp", + "--input=../../build/dependencies/schema/standard_library", + "--version=13.0.0" + ] + }, + { + "name": "C#", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_usr", + "--output=.spatialos/generated/usr", + "--language=csharp", + "--input=../../schema", + "--repository=../../build/dependencies/schema", + "--version=13.0.0" + ] + }, + { + "name": "Json AST standard library", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_json_std", + "--output=.spatialos/json/std", + "--language=ast_json", + "--input=../../build/dependencies/schema/standard_library", + "--version=13.0.0" + ] + }, + { + "name": "Json AST", + "arguments": [ + "process_schema", + "generate", + "--cachePath=.spatialos/cache/schema_codegen_cache_json_usr", + "--output=.spatialos/json/usr", + "--language=ast_json", + "--input=../../schema", + "--repository=../../build/dependencies/schema", + "--version=13.0.0" + ] + }, + { + "name": "Unity Code Generation", + "description": "Generates Unity MonoBehaviours, readers and writers from the project schema.", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-mono-invoker", + "arguments": [ + "Improbable/bin/UnityCodeGenerator.exe", + "--", + "--json-dir=.spatialos/json", + "--unity-component-output-dir=Assets/Plugins/Improbable/Generated/Components", + "--unity-editor-component-output-dir=Assets/Plugins/Improbable/Editor/Generated/Components", + "--reader-writer-output-dir=.spatialos/generated/readers_writers" + ] + }, + { + "name": "Compile generated scripts", + "description": "Compiles the generated code into a single DLL for Unity to consume.", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-csharp-invoker", + "arguments": [ + "--unity-reference=UnityEngine.dll", + "--", + "-lib:Assets/Plugins/Improbable/Sdk/Dll", + "-reference:Improbable.WorkerSdkCsharp.dll,Improbable.WorkerSdkCsharp.Framework.dll", + "-target:library", + "-debug", + "-unsafe", + "-nowarn:1591", + "-recurse:.spatialos/generated/*.cs", + "-recurse:Assets/Plugins/Improbable/.UnityPartials/*.cs", + "-out:Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.dll", + "-doc:Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.xml" + ] + } + ] + }, + { + "name": "Build", + "steps": [ + { + "name": "Codegen", + "arguments": [ + "invoke-task", + "Codegen" + ] + }, + { + "name": "UnityWorker workers", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/unity-invoker", + "arguments": [ + "--", + "-batchmode", + "-quit", + "-nographics", + "-logfile", + "../../logs/${IMPROBABLE_WORKER_NAME}/unity_invoke.log", + "-projectPath", + "${IMPROBABLE_WORKER_DIR}", + "-executeMethod", + "Improbable.Unity.EditorTools.Build.SimpleBuildSystem.Build", + "+buildWorkerTypes", + "${IMPROBABLE_WORKER_NAME}" + ] + } + ] + }, + { + "name": "Clean", + "steps": [ + { + "name": "Build artifacts and generated code", + "command": "Improbable/bin/${IMPROBABLE_PLATFORM_NAME}/clean", + "arguments": [ + ".spatialos", + "Assets/Plugins/Improbable/Generated", + "Assets/Plugins/Improbable/Editor/Generated/Components", + "Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.dll", + "Assets/Plugins/Improbable/Sdk/Dll/Generated.Code.xml", + "../../build/assembly/unity", + "../../build/assembly/${IMPROBABLE_WORKER_NAME}" + ] + } + ] + } + ] +} diff --git a/workers/unity/spatialos.UnityClient.worker.json b/workers/unity/spatialos.UnityClient.worker.json index c676281..de25188 100644 --- a/workers/unity/spatialos.UnityClient.worker.json +++ b/workers/unity/spatialos.UnityClient.worker.json @@ -1,7 +1,6 @@ { "build": { - "tasks_filename": "spatialos.unity.client.build.json", - "generated_build_scripts_type": "unity" + "tasks_filename": "Improbable/build_script/spatialos.unity.client.build.json" }, "bridge": { "worker_attribute_set": { diff --git a/workers/unity/spatialos.UnityWorker.worker.json b/workers/unity/spatialos.UnityWorker.worker.json index febb0d8..c19dcb1 100644 --- a/workers/unity/spatialos.UnityWorker.worker.json +++ b/workers/unity/spatialos.UnityWorker.worker.json @@ -1,7 +1,6 @@ { "build": { - "tasks_filename": "spatialos.unity.worker.build.json", - "generated_build_scripts_type": "unity" + "tasks_filename": "Improbable/build_script/spatialos.unity.worker.build.json" }, "bridge": { "worker_attribute_set": {