From b914f50cd9d3e9f9b0cf13f46d58d45b58eb9510 Mon Sep 17 00:00:00 2001 From: neuecc Date: Sun, 7 Jan 2024 19:58:09 +0900 Subject: [PATCH] Unity Impl --- README.md | 7 + .../AvaloniaDispatcherFrameProvider.cs | 2 +- src/R3.Unity/Assets/R3.Unity.meta | 8 + src/R3.Unity/Assets/R3.Unity/Editor.meta | 8 + .../R3.Unity/Editor/EditorEnableState.cs | 47 +++ .../R3.Unity/Editor/EditorEnableState.cs.meta | 11 + .../Editor/ObservableTrackerTreeView.cs | 173 ++++++++++ .../Editor/ObservableTrackerTreeView.cs.meta | 11 + .../Editor/ObservableTrackerWindow.cs | 227 ++++++++++++++ .../Editor/ObservableTrackerWindow.cs.meta | 11 + .../R3.Unity/Editor/R3.Unity.Editor.asmdef | 17 + .../Editor/R3.Unity.Editor.asmdef.meta | 7 + .../R3.Unity/Editor/SplitterGUILayout.cs | 62 ++++ .../R3.Unity/Editor/SplitterGUILayout.cs.meta | 11 + src/R3.Unity/Assets/R3.Unity/Runtime.meta | 8 + .../Runtime/MonoBehaviourExtensions.cs | 18 ++ .../Runtime/MonoBehaviourExtensions.cs.meta | 11 + .../R3.Unity/Runtime/ObserveOnExtensions.cs | 15 + .../Runtime/ObserveOnExtensions.cs.meta | 11 + .../R3.Unity/Runtime/PlayerLoopHelper.cs | 173 ++++++++++ .../R3.Unity/Runtime/PlayerLoopHelper.cs.meta | 11 + .../Assets/R3.Unity/Runtime/R3.Unity.asmdef | 18 ++ .../R3.Unity/Runtime/R3.Unity.asmdef.meta | 7 + .../R3.Unity/Runtime/UnityEventExtensions.cs | 51 +++ .../Runtime/UnityEventExtensions.cs.meta | 11 + .../R3.Unity/Runtime/UnityFrameProvider.cs | 76 +++++ .../Runtime/UnityFrameProvider.cs.meta | 11 + .../Runtime/UnityGraphicExtensions.cs | 38 +++ .../Runtime/UnityGraphicExtensions.cs.meta | 11 + .../Runtime/UnityProviderInitializer.cs | 21 ++ .../Runtime/UnityProviderInitializer.cs.meta | 11 + .../R3.Unity/Runtime/UnityTimeProvider.cs | 296 ++++++++++++++++++ .../Runtime/UnityTimeProvider.cs.meta | 11 + .../Runtime/UnityUIComponentExtensions.cs | 103 ++++++ .../UnityUIComponentExtensions.cs.meta | 11 + src/R3.Unity/Assets/R3.Unity/package.json | 12 + .../Assets/R3.Unity/package.json.meta | 7 + .../Assets/Scenes/NewBehaviourScript.cs | 41 +++ .../Assets/Scenes/NewBehaviourScript.cs.meta | 11 + src/R3.Unity/Assets/Scenes/SampleScene.unity | 14 + .../ProjectSettings/ProjectVersion.txt | 4 +- .../SceneTemplateSettings.json | 167 ++++++++++ src/R3.Unity/ProjectSettings/boot.config | 0 src/R3.WPF/WpfRenderingFrameProvider.cs | 2 +- src/R3/Collections/FreeListCore.cs | 6 +- src/R3/SubscriptionTracker.cs | 3 - 46 files changed, 1784 insertions(+), 8 deletions(-) create mode 100644 src/R3.Unity/Assets/R3.Unity.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/EditorEnableState.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/EditorEnableState.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerTreeView.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerTreeView.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerWindow.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerWindow.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/R3.Unity.Editor.asmdef create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/R3.Unity.Editor.asmdef.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/SplitterGUILayout.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Editor/SplitterGUILayout.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/MonoBehaviourExtensions.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/MonoBehaviourExtensions.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/ObserveOnExtensions.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/ObserveOnExtensions.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/PlayerLoopHelper.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/PlayerLoopHelper.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/R3.Unity.asmdef create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/R3.Unity.asmdef.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityEventExtensions.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityEventExtensions.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityFrameProvider.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityFrameProvider.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityGraphicExtensions.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityGraphicExtensions.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityProviderInitializer.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityProviderInitializer.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityTimeProvider.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityTimeProvider.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityUIComponentExtensions.cs create mode 100644 src/R3.Unity/Assets/R3.Unity/Runtime/UnityUIComponentExtensions.cs.meta create mode 100644 src/R3.Unity/Assets/R3.Unity/package.json create mode 100644 src/R3.Unity/Assets/R3.Unity/package.json.meta create mode 100644 src/R3.Unity/Assets/Scenes/NewBehaviourScript.cs create mode 100644 src/R3.Unity/Assets/Scenes/NewBehaviourScript.cs.meta create mode 100644 src/R3.Unity/ProjectSettings/SceneTemplateSettings.json delete mode 100644 src/R3.Unity/ProjectSettings/boot.config diff --git a/README.md b/README.md index c3c9649c..e87fa9f4 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,10 @@ public abstract class Observer : IDisposable public void OnCompleted(Result result); } ``` + + + +## Unity + +lower supported version: Unity 2021.3 + diff --git a/src/R3.Avalonia/AvaloniaDispatcherFrameProvider.cs b/src/R3.Avalonia/AvaloniaDispatcherFrameProvider.cs index 593f8a34..7622acbb 100644 --- a/src/R3.Avalonia/AvaloniaDispatcherFrameProvider.cs +++ b/src/R3.Avalonia/AvaloniaDispatcherFrameProvider.cs @@ -8,7 +8,7 @@ namespace R3.Avalonia; // NOTE: idially, not polling, use like the WPF's CompositionTarget.Rendering -public sealed class AvaloniaDispatcherFrameProvider : FrameProvider +public sealed class AvaloniaDispatcherFrameProvider : FrameProvider, IDisposable { bool disposed; long frameCount; diff --git a/src/R3.Unity/Assets/R3.Unity.meta b/src/R3.Unity/Assets/R3.Unity.meta new file mode 100644 index 00000000..d3319947 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7f945005998564e4e94663edb0f71b18 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Editor.meta b/src/R3.Unity/Assets/R3.Unity/Editor.meta new file mode 100644 index 00000000..d175c56b --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8dfacb58d7567834fbd8a4a006915cfd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/EditorEnableState.cs b/src/R3.Unity/Assets/R3.Unity/Editor/EditorEnableState.cs new file mode 100644 index 00000000..540deacb --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/EditorEnableState.cs @@ -0,0 +1,47 @@ +using UnityEditor; + +namespace R3.Unity.Editor +{ + public static class EditorEnableState + { + const string EnableAutoReloadKey = "ObservableTrackerWindow_EnableAutoReloadKey"; + const string EnableTrackingKey = "ObservableTrackerWindow_EnableTrackingKey"; + const string EnableStackTraceKey = "ObservableTrackerWindow_EnableStackTraceKey"; + + public static bool EnableAutoReload + { + get + { + return EditorPrefs.GetBool(EnableAutoReloadKey, false); + } + set + { + UnityEditor.EditorPrefs.SetBool(EnableAutoReloadKey, value); + } + } + + public static bool EnableTracking + { + get + { + return UnityEditor.EditorPrefs.GetBool(EnableTrackingKey, false); + } + set + { + UnityEditor.EditorPrefs.SetBool(EnableTrackingKey, value); + } + } + + public static bool EnableStackTrace + { + get + { + return UnityEditor.EditorPrefs.GetBool(EnableStackTraceKey, false); + } + set + { + UnityEditor.EditorPrefs.SetBool(EnableStackTraceKey, value); + } + } + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/EditorEnableState.cs.meta b/src/R3.Unity/Assets/R3.Unity/Editor/EditorEnableState.cs.meta new file mode 100644 index 00000000..dacebc30 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/EditorEnableState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dcb7a4c0bad64844a9826d605a62c7fd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerTreeView.cs b/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerTreeView.cs new file mode 100644 index 00000000..a5c65c46 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerTreeView.cs @@ -0,0 +1,173 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using UnityEditor; +using UnityEditor.IMGUI.Controls; +using UnityEngine; + +namespace R3.Unity.Editor +{ + public class ObservableTrackerViewItem : TreeViewItem + { + static Regex removeHref = new Regex("(.+)", RegexOptions.Compiled); + + public string Type { get; set; } + public string Elapsed { get; set; } + + string position; + public string Position + { + get { return position; } + set + { + position = value; + PositionFirstLine = GetFirstLine(position); + } + } + + public string PositionFirstLine { get; private set; } + + static string GetFirstLine(string str) + { + var sb = new StringBuilder(); + for (int i = 0; i < str.Length; i++) + { + if (str[i] == '\r' || str[i] == '\n') + { + break; + } + sb.Append(str[i]); + } + + return removeHref.Replace(sb.ToString(), "$1"); + } + + public ObservableTrackerViewItem(int id) : base(id) + { + + } + } + + public class ObservableTrackerTreeView : TreeView + { + const string sortedColumnIndexStateKey = "UniTaskTrackerTreeView_sortedColumnIndex"; + + public IReadOnlyList CurrentBindingItems; + + public ObservableTrackerTreeView() + : this(new TreeViewState(), new MultiColumnHeader(new MultiColumnHeaderState(new[] + { + new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Type"), width = 20}, + new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Elapsed"), width = 10}, + new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Position")}, + }))) + { + } + + ObservableTrackerTreeView(TreeViewState state, MultiColumnHeader header) + : base(state, header) + { + rowHeight = 20; + showAlternatingRowBackgrounds = true; + showBorder = true; + header.sortingChanged += Header_sortingChanged; + + header.ResizeToFit(); + Reload(); + + header.sortedColumnIndex = SessionState.GetInt(sortedColumnIndexStateKey, 1); + } + + public void ReloadAndSort() + { + var currentSelected = this.state.selectedIDs; + Reload(); + Header_sortingChanged(this.multiColumnHeader); + this.state.selectedIDs = currentSelected; + } + + private void Header_sortingChanged(MultiColumnHeader multiColumnHeader) + { + SessionState.SetInt(sortedColumnIndexStateKey, multiColumnHeader.sortedColumnIndex); + var index = multiColumnHeader.sortedColumnIndex; + var ascending = multiColumnHeader.IsSortedAscending(multiColumnHeader.sortedColumnIndex); + + var items = rootItem.children.Cast(); + + IOrderedEnumerable orderedEnumerable; + switch (index) + { + case 0: + orderedEnumerable = ascending ? items.OrderBy(item => item.Type) : items.OrderByDescending(item => item.Type); + break; + case 1: + orderedEnumerable = ascending ? items.OrderBy(item => double.Parse(item.Elapsed)) : items.OrderByDescending(item => double.Parse(item.Elapsed)); + break; + case 2: + orderedEnumerable = ascending ? items.OrderBy(item => item.Position) : items.OrderByDescending(item => item.Position); + break; + default: + throw new ArgumentOutOfRangeException(nameof(index), index, null); + } + + CurrentBindingItems = rootItem.children = orderedEnumerable.Cast().ToList(); + BuildRows(rootItem); + } + + protected override TreeViewItem BuildRoot() + { + var root = new TreeViewItem { depth = -1 }; + + var children = new List(); + + var now = DateTime.Now; // tracking state is using local Now. + SubscriptionTracker.ForEachActiveTask(state => + { + children.Add(new ObservableTrackerViewItem(state.TrackingId) { Type = state.FormattedType, Elapsed = (now - state.AddTime).TotalSeconds.ToString("00.00"), Position = state.StackTrace }); + }); + + CurrentBindingItems = children; + root.children = CurrentBindingItems as List; + return root; + } + + protected override bool CanMultiSelect(TreeViewItem item) + { + return false; + } + + protected override void RowGUI(RowGUIArgs args) + { + var item = args.item as ObservableTrackerViewItem; + + for (var visibleColumnIndex = 0; visibleColumnIndex < args.GetNumVisibleColumns(); visibleColumnIndex++) + { + var rect = args.GetCellRect(visibleColumnIndex); + var columnIndex = args.GetColumn(visibleColumnIndex); + + var labelStyle = args.selected ? EditorStyles.whiteLabel : EditorStyles.label; + labelStyle.alignment = TextAnchor.MiddleLeft; + switch (columnIndex) + { + case 0: + EditorGUI.LabelField(rect, item.Type, labelStyle); + break; + case 1: + EditorGUI.LabelField(rect, item.Elapsed, labelStyle); + break; + case 2: + EditorGUI.LabelField(rect, item.PositionFirstLine, labelStyle); + break; + default: + throw new ArgumentOutOfRangeException(nameof(columnIndex), columnIndex, null); + } + } + } + } + +} + diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerTreeView.cs.meta b/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerTreeView.cs.meta new file mode 100644 index 00000000..931071f6 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerTreeView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb169fd740c0ebd459cbfe2b76c8687a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerWindow.cs b/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerWindow.cs new file mode 100644 index 00000000..d4177a1c --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerWindow.cs @@ -0,0 +1,227 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System; +using UnityEditor.IMGUI.Controls; + +namespace R3.Unity.Editor +{ + public class ObservableTrackerWindow : EditorWindow + { + static int interval; + + static ObservableTrackerWindow window; + + [MenuItem("Window/Observable Tracker")] + public static void OpenWindow() + { + if (window != null) + { + window.Close(); + } + + // will called OnEnable(singleton instance will be set). + GetWindow("Observable Tracker").Show(); + } + + static readonly GUILayoutOption[] EmptyLayoutOption = new GUILayoutOption[0]; + + ObservableTrackerTreeView treeView; + object splitterState; + + // state on window + static bool enableAutoReload; + static bool enableTracking; + static bool enableStackTrace; + + void OnEnable() + { + window = this; // set singleton. + splitterState = SplitterGUILayout.CreateSplitterState(new float[] { 75f, 25f }, new int[] { 32, 32 }, null); + treeView = new ObservableTrackerTreeView(); + + // restore settings from EditorPrefs. + enableAutoReload = EditorEnableState.EnableAutoReload; + enableTracking = EditorEnableState.EnableTracking; + enableStackTrace = EditorEnableState.EnableStackTrace; + } + + void OnGUI() + { + // Head + RenderHeadPanel(); + + // Splittable + SplitterGUILayout.BeginVerticalSplit(this.splitterState, EmptyLayoutOption); + { + // Column Tabble + RenderTable(); + + // StackTrace details + RenderDetailsPanel(); + } + SplitterGUILayout.EndVerticalSplit(); + } + + #region HeadPanel + + static readonly GUIContent EnableAutoReloadHeadContent = EditorGUIUtility.TrTextContent("Enable AutoReload", "Reload automatically.", (Texture)null); + static readonly GUIContent ReloadHeadContent = EditorGUIUtility.TrTextContent("Reload", "Reload View.", (Texture)null); + static readonly GUIContent GCHeadContent = EditorGUIUtility.TrTextContent("GC.Collect", "Invoke GC.Collect.", (Texture)null); + static readonly GUIContent EnableTrackingHeadContent = EditorGUIUtility.TrTextContent("Enable Tracking", "Start to track Observable subscription. Performance impact: low", (Texture)null); + static readonly GUIContent EnableStackTraceHeadContent = EditorGUIUtility.TrTextContent("Enable StackTrace", "Capture StackTrace when subscribed. Performance impact: high", (Texture)null); + + // [Enable Tracking] | [Enable StackTrace] + void RenderHeadPanel() + { + EditorGUILayout.BeginVertical(EmptyLayoutOption); + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar, EmptyLayoutOption); + + if (GUILayout.Toggle(enableAutoReload, EnableAutoReloadHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption) != enableAutoReload) + { + EditorEnableState.EnableAutoReload = enableAutoReload = !enableAutoReload; + } + + if (GUILayout.Toggle(enableTracking, EnableTrackingHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption) != enableTracking) + { + EditorEnableState.EnableTracking = enableTracking = !enableTracking; + } + + if (GUILayout.Toggle(enableStackTrace, EnableStackTraceHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption) != enableStackTrace) + { + EditorEnableState.EnableStackTrace = enableStackTrace = !enableStackTrace; + } + + GUILayout.FlexibleSpace(); + + if (GUILayout.Button(ReloadHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption)) + { + SubscriptionTracker.CheckAndResetDirty(); + treeView.ReloadAndSort(); + Repaint(); + } + + if (GUILayout.Button(GCHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption)) + { + GC.Collect(0); + } + + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + } + + #endregion + + #region TableColumn + + Vector2 tableScroll; + GUIStyle tableListStyle; + + void RenderTable() + { + if (tableListStyle == null) + { + tableListStyle = new GUIStyle("CN Box"); + tableListStyle.margin.top = 0; + tableListStyle.padding.left = 3; + } + + EditorGUILayout.BeginVertical(tableListStyle, EmptyLayoutOption); + + this.tableScroll = EditorGUILayout.BeginScrollView(this.tableScroll, new GUILayoutOption[] + { + GUILayout.ExpandWidth(true), + GUILayout.MaxWidth(2000f) + }); + var controlRect = EditorGUILayout.GetControlRect(new GUILayoutOption[] + { + GUILayout.ExpandHeight(true), + GUILayout.ExpandWidth(true) + }); + + + treeView?.OnGUI(controlRect); + + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + } + + private void Update() + { + // reflect to SubscriptionTracker + SubscriptionTracker.EnableTracking = enableTracking; + SubscriptionTracker.EnableStackTrace = enableStackTrace; + + //var count = 0; + //SubscriptionTracker.ForEachActiveTask(_ => count++); + //Debug.Log(count); + + if (enableAutoReload) + { + if (interval++ % 120 == 0) + { + if (SubscriptionTracker.CheckAndResetDirty()) + { + treeView.ReloadAndSort(); + Repaint(); + } + } + } + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] + public static void SetSubscriptionTrackerValues() + { + SubscriptionTracker.EnableTracking = EditorEnableState.EnableTracking; + SubscriptionTracker.EnableStackTrace = EditorEnableState.EnableStackTrace; + } + + #endregion + + #region Details + + static GUIStyle detailsStyle; + Vector2 detailsScroll; + + void RenderDetailsPanel() + { + if (detailsStyle == null) + { + detailsStyle = new GUIStyle("CN Message"); + detailsStyle.wordWrap = false; + detailsStyle.stretchHeight = true; + detailsStyle.margin.right = 15; + } + + string message = ""; + var selected = treeView.state.selectedIDs; + if (selected.Count > 0) + { + var first = selected[0]; + var item = treeView.CurrentBindingItems.FirstOrDefault(x => x.id == first) as ObservableTrackerViewItem; + if (item != null) + { + message = item.Position; + } + } + + detailsScroll = EditorGUILayout.BeginScrollView(this.detailsScroll, EmptyLayoutOption); + var vector = detailsStyle.CalcSize(new GUIContent(message)); + EditorGUILayout.SelectableLabel(message, detailsStyle, new GUILayoutOption[] + { + GUILayout.ExpandHeight(true), + GUILayout.ExpandWidth(true), + GUILayout.MinWidth(vector.x), + GUILayout.MinHeight(vector.y) + }); + EditorGUILayout.EndScrollView(); + } + + #endregion + } +} + diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerWindow.cs.meta b/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerWindow.cs.meta new file mode 100644 index 00000000..f398a47d --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/ObservableTrackerWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54ec5737689776f48bc8358aba501f1a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/R3.Unity.Editor.asmdef b/src/R3.Unity/Assets/R3.Unity/Editor/R3.Unity.Editor.asmdef new file mode 100644 index 00000000..9753c4b3 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/R3.Unity.Editor.asmdef @@ -0,0 +1,17 @@ +{ + "name": "R3.Unity.Editor", + "references": [ + "R3.Unity" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": false, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/R3.Unity.Editor.asmdef.meta b/src/R3.Unity/Assets/R3.Unity/Editor/R3.Unity.Editor.asmdef.meta new file mode 100644 index 00000000..5012bc60 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/R3.Unity.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f8c68d614fe30d647996098360b5e0d5 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/SplitterGUILayout.cs b/src/R3.Unity/Assets/R3.Unity/Editor/SplitterGUILayout.cs new file mode 100644 index 00000000..a68edc65 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/SplitterGUILayout.cs @@ -0,0 +1,62 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using System; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEngine; + +namespace R3.Unity.Editor +{ + // reflection call of UnityEditor.SplitterGUILayout + internal static class SplitterGUILayout + { + static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; + + static Lazy splitterStateType = new Lazy(() => + { + var type = typeof(EditorWindow).Assembly.GetTypes().First(x => x.FullName == "UnityEditor.SplitterState"); + return type; + }); + + static Lazy splitterStateCtor = new Lazy(() => + { + var type = splitterStateType.Value; + return type.GetConstructor(flags, null, new Type[] { typeof(float[]), typeof(int[]), typeof(int[]) }, null); + }); + + static Lazy splitterGUILayoutType = new Lazy(() => + { + var type = typeof(EditorWindow).Assembly.GetTypes().First(x => x.FullName == "UnityEditor.SplitterGUILayout"); + return type; + }); + + static Lazy beginVerticalSplit = new Lazy(() => + { + var type = splitterGUILayoutType.Value; + return type.GetMethod("BeginVerticalSplit", flags, null, new Type[] { splitterStateType.Value, typeof(GUILayoutOption[]) }, null); + }); + + static Lazy endVerticalSplit = new Lazy(() => + { + var type = splitterGUILayoutType.Value; + return type.GetMethod("EndVerticalSplit", flags, null, Type.EmptyTypes, null); + }); + + public static object CreateSplitterState(float[] relativeSizes, int[] minSizes, int[] maxSizes) + { + return splitterStateCtor.Value.Invoke(new object[] { relativeSizes, minSizes, maxSizes }); + } + + public static void BeginVerticalSplit(object splitterState, params GUILayoutOption[] options) + { + beginVerticalSplit.Value.Invoke(null, new object[] { splitterState, options }); + } + + public static void EndVerticalSplit() + { + endVerticalSplit.Value.Invoke(null, Type.EmptyTypes); + } + } +} + diff --git a/src/R3.Unity/Assets/R3.Unity/Editor/SplitterGUILayout.cs.meta b/src/R3.Unity/Assets/R3.Unity/Editor/SplitterGUILayout.cs.meta new file mode 100644 index 00000000..ac96ca59 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Editor/SplitterGUILayout.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 14cc21ce00a64784eab22336b747c4f8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime.meta b/src/R3.Unity/Assets/R3.Unity/Runtime.meta new file mode 100644 index 00000000..ff959016 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: df6f082a67ae96342ae0a4e0745f52bf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/MonoBehaviourExtensions.cs b/src/R3.Unity/Assets/R3.Unity/Runtime/MonoBehaviourExtensions.cs new file mode 100644 index 00000000..16ab8448 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/MonoBehaviourExtensions.cs @@ -0,0 +1,18 @@ +using System.Threading; +using UnityEngine; + +namespace R3 +{ + internal static class MonoBehaviourExtensions + { + public static CancellationToken GetDestroyCancellationToken(this MonoBehaviour value) + { + // UNITY_2022_2_OR_NEWER has MonoBehavior.destroyCancellationToken +#if UNITY_2022_2_OR_NEWER + return value.destroyCancellationToken; +#else + return CancellationToken.None;; +#endif + } + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/MonoBehaviourExtensions.cs.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/MonoBehaviourExtensions.cs.meta new file mode 100644 index 00000000..0f7528fa --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/MonoBehaviourExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b4768aaec75c8ad4bb78d266fca5704c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/ObserveOnExtensions.cs b/src/R3.Unity/Assets/R3.Unity/Runtime/ObserveOnExtensions.cs new file mode 100644 index 00000000..944dd18f --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/ObserveOnExtensions.cs @@ -0,0 +1,15 @@ +namespace R3 // using R3 +{ + public static class ObserveOnExtensions + { + public static Observable ObserveOnMainThread(this Observable source) + { + return source.ObserveOn(UnityFrameProvider.Update); + } + + public static Observable SubscribeOnMainThread(this Observable source) + { + return source.SubscribeOn(UnityFrameProvider.Update); + } + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/ObserveOnExtensions.cs.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/ObserveOnExtensions.cs.meta new file mode 100644 index 00000000..ce572419 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/ObserveOnExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ddacbc050f511cc408f795b787d03a0b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/PlayerLoopHelper.cs b/src/R3.Unity/Assets/R3.Unity/Runtime/PlayerLoopHelper.cs new file mode 100644 index 00000000..f226aa32 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/PlayerLoopHelper.cs @@ -0,0 +1,173 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +using System; +using System.Linq; +using UnityEngine; +using UnityEngine.LowLevel; +using PlayerLoopType = UnityEngine.PlayerLoop; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace R3 +{ + public static class R3LoopRunners + { + public struct Initialization { }; + public struct EarlyUpdate { }; + public struct FixedUpdate { }; + public struct PreUpdate { }; + public struct Update { }; + public struct PreLateUpdate { }; + public struct PostLateUpdate { }; + public struct TimeUpdate { }; + } + + public enum PlayerLoopTiming + { + Initialization = 0, + EarlyUpdate = 1, + FixedUpdate = 2, + PreUpdate = 3, + Update = 4, + PreLateUpdate = 5, + PostLateUpdate = 6, + TimeUpdate = 7, + } + + public static class PlayerLoopHelper + { + internal static string ApplicationDataPath => applicationDataPath; // used for editor window + static string applicationDataPath; + + static UnityFrameProvider[] runners; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] + static void Init() + { + try + { + applicationDataPath = Application.dataPath; + } + catch { } + +#if UNITY_EDITOR + // When domain reload is disabled, re-initialization is required when entering play mode; + // otherwise, pending tasks will leak between play mode sessions. + var domainReloadDisabled = UnityEditor.EditorSettings.enterPlayModeOptionsEnabled && + UnityEditor.EditorSettings.enterPlayModeOptions.HasFlag(UnityEditor.EnterPlayModeOptions.DisableDomainReload); + if (!domainReloadDisabled && runners != null) return; +#else + if (runners != null) return; // already initialized +#endif + + var playerLoop = PlayerLoop.GetCurrentPlayerLoop(); + Initialize(ref playerLoop); + } + + public static void Initialize(ref PlayerLoopSystem playerLoop) + { + runners = new UnityFrameProvider[8]; + + var newLoop = playerLoop.subSystemList.ToArray(); + + // Initialization + InsertLoop(newLoop, typeof(PlayerLoopType.Initialization), typeof(R3LoopRunners.Initialization), runners[0] = (UnityFrameProvider)UnityFrameProvider.Initialization); + InsertLoop(newLoop, typeof(PlayerLoopType.EarlyUpdate), typeof(R3LoopRunners.EarlyUpdate), runners[1] = (UnityFrameProvider)UnityFrameProvider.EarlyUpdate); + InsertLoop(newLoop, typeof(PlayerLoopType.FixedUpdate), typeof(R3LoopRunners.FixedUpdate), runners[2] = (UnityFrameProvider)UnityFrameProvider.FixedUpdate); + InsertLoop(newLoop, typeof(PlayerLoopType.PreUpdate), typeof(R3LoopRunners.PreUpdate), runners[3] = (UnityFrameProvider)UnityFrameProvider.PreUpdate); + InsertLoop(newLoop, typeof(PlayerLoopType.Update), typeof(R3LoopRunners.Update), runners[4] = (UnityFrameProvider)UnityFrameProvider.Update); + InsertLoop(newLoop, typeof(PlayerLoopType.PreLateUpdate), typeof(R3LoopRunners.PreLateUpdate), runners[5] = (UnityFrameProvider)UnityFrameProvider.PreLateUpdate); + InsertLoop(newLoop, typeof(PlayerLoopType.PostLateUpdate), typeof(R3LoopRunners.PostLateUpdate), runners[6] = (UnityFrameProvider)UnityFrameProvider.PostLateUpdate); + InsertLoop(newLoop, typeof(PlayerLoopType.TimeUpdate), typeof(R3LoopRunners.TimeUpdate), runners[7] = (UnityFrameProvider)UnityFrameProvider.TimeUpdate); + + playerLoop.subSystemList = newLoop; + PlayerLoop.SetPlayerLoop(playerLoop); + } + + static void InsertLoop(PlayerLoopSystem[] loopSystems, Type loopType, Type loopRunnerType, UnityFrameProvider frameProvider) + { + var i = FindLoopSystemIndex(loopSystems, loopType); + ref var loop = ref loopSystems[i]; + loop.subSystemList = InsertRunner(loop.subSystemList, loopRunnerType, frameProvider); + } + + static int FindLoopSystemIndex(PlayerLoopSystem[] playerLoopList, Type systemType) + { + for (int i = 0; i < playerLoopList.Length; i++) + { + if (playerLoopList[i].type == systemType) + { + return i; + } + } + + throw new Exception("Target PlayerLoopSystem does not found. Type:" + systemType.FullName); + } + + static PlayerLoopSystem[] InsertRunner(PlayerLoopSystem[] subSystemList, Type loopRunnerType, UnityFrameProvider runner) + { + +#if UNITY_EDITOR + EditorApplication.playModeStateChanged += (state) => + { + if (state == PlayModeStateChange.EnteredEditMode || state == PlayModeStateChange.ExitingEditMode) + { + // run rest action before clear. + if (runner != null) + { + runner.Run(); + runner.Clear(); + } + } + }; +#endif + + var source = subSystemList.Where(x => x.type != loopRunnerType).ToArray(); // remove duplicate(initialized previously) + var dest = new PlayerLoopSystem[source.Length + 1]; + + Array.Copy(source, 0, dest, 1, source.Length); + + // insert first + dest[0] = new PlayerLoopSystem + { + type = loopRunnerType, + updateDelegate = runner.Run + }; + + return dest; + } + +#if UNITY_EDITOR + + [InitializeOnLoadMethod] + static void InitOnEditor() + { + // Execute the play mode init method + Init(); + + // register an Editor update delegate, used to forcing playerLoop update + EditorApplication.update += ForceEditorPlayerLoopUpdate; + } + + private static void ForceEditorPlayerLoopUpdate() + { + if (EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling || EditorApplication.isUpdating) + { + // Not in Edit mode, don't interfere + return; + } + + if (runners != null) + { + foreach (var item in runners) + { + if (item != null) item.Run(); + } + } + } + +#endif + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/PlayerLoopHelper.cs.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/PlayerLoopHelper.cs.meta new file mode 100644 index 00000000..f277eed7 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/PlayerLoopHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fec43b7564edb8246a071f06ebbf0b8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/R3.Unity.asmdef b/src/R3.Unity/Assets/R3.Unity/Runtime/R3.Unity.asmdef new file mode 100644 index 00000000..e702b269 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/R3.Unity.asmdef @@ -0,0 +1,18 @@ +{ + "name": "R3.Unity", + "rootNamespace": "R3", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [ + "R3.dll", + "Microsoft.Bcl.TimeProvider.dll", + "Microsoft.Bcl.AsyncInterfaces.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/R3.Unity.asmdef.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/R3.Unity.asmdef.meta new file mode 100644 index 00000000..6738adce --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/R3.Unity.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 77221876cc6b8244180b96e320b1bcd4 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityEventExtensions.cs b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityEventExtensions.cs new file mode 100644 index 00000000..844549b4 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityEventExtensions.cs @@ -0,0 +1,51 @@ +using System.Threading; +using UnityEngine.Events; + +namespace R3 +{ + public static class UnityEventExtensions + { + public static Observable AsObservable(this UnityEngine.Events.UnityEvent unityEvent, CancellationToken cancellationToken = default) + { + return Observable.FromEvent(h => new UnityAction(h), h => unityEvent.AddListener(h), h => unityEvent.RemoveListener(h), cancellationToken); + } + + public static Observable AsObservable(this UnityEngine.Events.UnityEvent unityEvent, CancellationToken cancellationToken = default) + { + return Observable.FromEvent, T>(h => new UnityAction(h), h => unityEvent.AddListener(h), h => unityEvent.RemoveListener(h), cancellationToken); + } + + public static Observable<(T0 Arg0, T1 Arg1)> AsObservable(this UnityEngine.Events.UnityEvent unityEvent, CancellationToken cancellationToken = default) + { + return Observable.FromEvent, (T0, T1)>(h => + { + return new UnityAction((t0, t1) => + { + h((t0, t1)); + }); + }, h => unityEvent.AddListener(h), h => unityEvent.RemoveListener(h), cancellationToken); + } + + public static Observable<(T0 Arg0, T1 Arg1, T2 Arg2)> AsObservable(this UnityEngine.Events.UnityEvent unityEvent, CancellationToken cancellationToken = default) + { + return Observable.FromEvent, (T0, T1, T2)>(h => + { + return new UnityAction((t0, t1, t2) => + { + h((t0, t1, t2)); + }); + }, h => unityEvent.AddListener(h), h => unityEvent.RemoveListener(h), cancellationToken); + } + + public static Observable<(T0 Arg0, T1 Arg1, T2 Arg2, T3 Arg3)> AsObservable(this UnityEngine.Events.UnityEvent unityEvent, CancellationToken cancellationToken = default) + { + return Observable.FromEvent, (T0, T1, T2, T3)>(h => + { + return new UnityAction((t0, t1, t2, t3) => + { + h((t0, t1, t2, t3)); + }); + }, h => unityEvent.AddListener(h), h => unityEvent.RemoveListener(h), cancellationToken); + } + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityEventExtensions.cs.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityEventExtensions.cs.meta new file mode 100644 index 00000000..b253c73d --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityEventExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c39b1e6add48ef24c95fd640b08622ef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityFrameProvider.cs b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityFrameProvider.cs new file mode 100644 index 00000000..2e77c7b6 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityFrameProvider.cs @@ -0,0 +1,76 @@ +using R3.Collections; +using System; +using UnityEngine; + +namespace R3 +{ + public class UnityFrameProvider : FrameProvider + { + public static readonly FrameProvider Initialization = new UnityFrameProvider(PlayerLoopTiming.Initialization); + public static readonly FrameProvider EarlyUpdate = new UnityFrameProvider(PlayerLoopTiming.EarlyUpdate); + public static readonly FrameProvider FixedUpdate = new UnityFrameProvider(PlayerLoopTiming.FixedUpdate); + public static readonly FrameProvider PreUpdate = new UnityFrameProvider(PlayerLoopTiming.PreUpdate); + public static readonly FrameProvider Update = new UnityFrameProvider(PlayerLoopTiming.Update); + public static readonly FrameProvider PreLateUpdate = new UnityFrameProvider(PlayerLoopTiming.PreLateUpdate); + public static readonly FrameProvider PostLateUpdate = new UnityFrameProvider(PlayerLoopTiming.PostLateUpdate); + public static readonly FrameProvider TimeUpdate = new UnityFrameProvider(PlayerLoopTiming.TimeUpdate); + + FreeListCore list; + readonly object gate = new object(); + + public PlayerLoopTiming PlayerLoopTiming { get; } + + internal UnityFrameProvider(PlayerLoopTiming playerLoopTiming) + { + this.PlayerLoopTiming = playerLoopTiming; + this.list = new FreeListCore(gate); + } + + public override long GetFrameCount() + { + return Time.frameCount; + } + + public override void Register(IFrameRunnerWorkItem callback) + { + list.Add(callback, out _); + } + + // called from PlayerLoop + + internal void Run() + { + long frameCount = Time.frameCount; + + var span = list.AsSpan(); + for (int i = 0; i < span.Length; i++) + { + ref readonly var item = ref span[i]; + if (item != null) + { + try + { + if (!item.MoveNext(frameCount)) + { + list.Remove(i); + } + } + catch (Exception ex) + { + list.Remove(i); + try + { + ObservableSystem.GetUnhandledExceptionHandler().Invoke(ex); + } + catch { } + } + } + } + } + + internal void Clear() + { + list.Clear(removeArray: true); + } + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityFrameProvider.cs.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityFrameProvider.cs.meta new file mode 100644 index 00000000..ce154c36 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityFrameProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1dca9d5e587a27346b74649eeec0df56 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityGraphicExtensions.cs b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityGraphicExtensions.cs new file mode 100644 index 00000000..bb54d793 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityGraphicExtensions.cs @@ -0,0 +1,38 @@ +using UnityEngine.Events; +using UnityEngine.UI; + +namespace R3 +{ + public static class UnityGraphicExtensions + { + public static Observable DirtyLayoutCallbackAsObservable(this Graphic graphic) + { + return Observable.Create(observer => + { + UnityAction registerHandler = () => observer.OnNext(Unit.Default); + graphic.RegisterDirtyLayoutCallback(registerHandler); + return Disposable.Create(static state => state.graphic.UnregisterDirtyLayoutCallback(state.registerHandler), (graphic, registerHandler)); + }); + } + + public static Observable DirtyMaterialCallbackAsObservable(this Graphic graphic) + { + return Observable.Create(observer => + { + UnityAction registerHandler = () => observer.OnNext(Unit.Default); + graphic.RegisterDirtyMaterialCallback(registerHandler); + return Disposable.Create(static state => state.graphic.UnregisterDirtyMaterialCallback(state.registerHandler), (graphic, registerHandler)); + }); + } + + public static Observable DirtyVerticesCallbackAsObservable(this Graphic graphic) + { + return Observable.Create(observer => + { + UnityAction registerHandler = () => observer.OnNext(Unit.Default); + graphic.RegisterDirtyVerticesCallback(registerHandler); + return Disposable.Create(static state => state.graphic.UnregisterDirtyVerticesCallback(state.registerHandler), (graphic, registerHandler)); + }); + } + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityGraphicExtensions.cs.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityGraphicExtensions.cs.meta new file mode 100644 index 00000000..1e14888d --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityGraphicExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4e4cecfabe4ed2546acd815359ce6f62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityProviderInitializer.cs b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityProviderInitializer.cs new file mode 100644 index 00000000..8c92ad92 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityProviderInitializer.cs @@ -0,0 +1,21 @@ +using System; +using UnityEngine; + +namespace R3 +{ + public static class UnityProviderInitializer + { + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)] + public static void SetDefaultObservableSystem() + { + SetDefaultObservableSystem(static ex => UnityEngine.Debug.LogException(ex)); + } + + public static void SetDefaultObservableSystem(Action unhandledExceptionHandler) + { + ObservableSystem.RegisterUnhandledExceptionHandler(unhandledExceptionHandler); + ObservableSystem.DefaultTimeProvider = UnityTimeProvider.Update; + ObservableSystem.DefaultFrameProvider = UnityFrameProvider.Update; + } + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityProviderInitializer.cs.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityProviderInitializer.cs.meta new file mode 100644 index 00000000..2f03896a --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityProviderInitializer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e0d541f39058cb4d9c10db7c3182f71 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityTimeProvider.cs b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityTimeProvider.cs new file mode 100644 index 00000000..55c4e0fa --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityTimeProvider.cs @@ -0,0 +1,296 @@ +using System; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using UnityEngine; + +namespace R3 +{ + public enum TimeKind + { + /// use Time.time, Time.deltaTime or Time.fixedTime, Time.fixedDeltaTime. + Time, + /// Ignore timescale, use Time.unscaledTime, Time.unscaledDeltaTime or Time.fixedUnscaledTime, Time.fixedUnscaledDeltaTime. + UnscaledTime, + /// use Time.realtimeSinceStartup, TimeProvider.System.GetTimestamp() + Realtime + } + + public class UnityTimeProvider : TimeProvider + { + public static readonly TimeProvider Initialization = new UnityTimeProvider(UnityFrameProvider.Initialization, TimeKind.Time); + public static readonly TimeProvider EarlyUpdate = new UnityTimeProvider(UnityFrameProvider.EarlyUpdate, TimeKind.Time); + public static readonly TimeProvider FixedUpdate = new UnityTimeProvider(UnityFrameProvider.FixedUpdate, TimeKind.Time); + public static readonly TimeProvider PreUpdate = new UnityTimeProvider(UnityFrameProvider.PreUpdate, TimeKind.Time); + public static readonly TimeProvider Update = new UnityTimeProvider(UnityFrameProvider.Update, TimeKind.Time); + public static readonly TimeProvider PreLateUpdate = new UnityTimeProvider(UnityFrameProvider.PreLateUpdate, TimeKind.Time); + public static readonly TimeProvider PostLateUpdate = new UnityTimeProvider(UnityFrameProvider.PostLateUpdate, TimeKind.Time); + public static readonly TimeProvider TimeUpdate = new UnityTimeProvider(UnityFrameProvider.TimeUpdate, TimeKind.Time); + + public static readonly TimeProvider InitializationIgnoreTimeScale = new UnityTimeProvider(UnityFrameProvider.Initialization, TimeKind.UnscaledTime); + public static readonly TimeProvider EarlyUpdateIgnoreTimeScale = new UnityTimeProvider(UnityFrameProvider.EarlyUpdate, TimeKind.UnscaledTime); + public static readonly TimeProvider FixedUpdateIgnoreTimeScale = new UnityTimeProvider(UnityFrameProvider.FixedUpdate, TimeKind.UnscaledTime); + public static readonly TimeProvider PreUpdateIgnoreTimeScale = new UnityTimeProvider(UnityFrameProvider.PreUpdate, TimeKind.UnscaledTime); + public static readonly TimeProvider UpdateIgnoreTimeScale = new UnityTimeProvider(UnityFrameProvider.Update, TimeKind.UnscaledTime); + public static readonly TimeProvider PreLateUpdateIgnoreTimeScale = new UnityTimeProvider(UnityFrameProvider.PreLateUpdate, TimeKind.UnscaledTime); + public static readonly TimeProvider PostLateUpdateIgnoreTimeScale = new UnityTimeProvider(UnityFrameProvider.PostLateUpdate, TimeKind.UnscaledTime); + public static readonly TimeProvider TimeUpdateIgnoreTimeScale = new UnityTimeProvider(UnityFrameProvider.TimeUpdate, TimeKind.UnscaledTime); + + public static readonly TimeProvider InitializationRealtime = new UnityTimeProvider(UnityFrameProvider.Initialization, TimeKind.Realtime); + public static readonly TimeProvider EarlyUpdateRealtime = new UnityTimeProvider(UnityFrameProvider.EarlyUpdate, TimeKind.Realtime); + public static readonly TimeProvider FixedUpdateRealtime = new UnityTimeProvider(UnityFrameProvider.FixedUpdate, TimeKind.Realtime); + public static readonly TimeProvider PreUpdateRealtime = new UnityTimeProvider(UnityFrameProvider.PreUpdate, TimeKind.Realtime); + public static readonly TimeProvider UpdateRealtime = new UnityTimeProvider(UnityFrameProvider.Update, TimeKind.Realtime); + public static readonly TimeProvider PreLateUpdateRealtime = new UnityTimeProvider(UnityFrameProvider.PreLateUpdate, TimeKind.Realtime); + public static readonly TimeProvider PostLateUpdateRealtime = new UnityTimeProvider(UnityFrameProvider.PostLateUpdate, TimeKind.Realtime); + public static readonly TimeProvider TimeUpdateRealtime = new UnityTimeProvider(UnityFrameProvider.TimeUpdate, TimeKind.Realtime); + + readonly UnityFrameProvider frameProvider; + readonly TimeKind timeKind; + + UnityTimeProvider(FrameProvider frameProvider, TimeKind timeKind) + { + this.frameProvider = (UnityFrameProvider)frameProvider; + this.timeKind = timeKind; + } + + public override long GetTimestamp() + { + if (frameProvider.PlayerLoopTiming == PlayerLoopTiming.FixedUpdate) + { + switch (timeKind) + { + case TimeKind.Time: + return (long)(Time.fixedTimeAsDouble * 100); + case TimeKind.UnscaledTime: + return (long)(Time.fixedUnscaledTimeAsDouble * 100); + default: + break; + } + } + else + { + switch (timeKind) + { + case TimeKind.Time: + return (long)(Time.timeAsDouble * 100); + case TimeKind.UnscaledTime: + return (long)(Time.unscaledTimeAsDouble * 100); + default: + break; + } + } + + return (long)(Time.realtimeSinceStartupAsDouble * 100); + } + + public override ITimer CreateTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) + { + return new FrameTimer(callback, state, dueTime, period, frameProvider, timeKind); + } + } + + internal sealed class FrameTimer : ITimer, IFrameRunnerWorkItem + { + enum RunningState + { + Stop, + RunningDueTime, + RunningPeriod, + ChangeRequested + } + + readonly TimerCallback callback; + readonly object state; + readonly UnityFrameProvider frameProvider; + readonly TimeKind timeKind; + readonly object gate = new object(); + + TimeSpan dueTime; + TimeSpan period; + RunningState runningState; + float elapsed; + bool isDisposed; + + // for DeltaType.Realtime + long lastTimestamp; + + public FrameTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period, UnityFrameProvider frameProvider, TimeKind timeKind) + { + this.callback = callback; + this.state = state; + this.dueTime = dueTime; + this.period = period; + this.frameProvider = frameProvider; + this.timeKind = timeKind; + Change(dueTime, period); + } + + public bool Change(TimeSpan dueTime, TimeSpan period) + { + if (isDisposed) return false; + + lock (gate) + { + this.dueTime = dueTime; + this.period = period; + + if (dueTime == Timeout.InfiniteTimeSpan) + { + if (runningState == RunningState.Stop) + { + return true; + } + } + + if (runningState == RunningState.Stop) + { + frameProvider.Register(this); + } + + runningState = RunningState.ChangeRequested; + } + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + float GetDeltaTime() + { + if (frameProvider.PlayerLoopTiming == PlayerLoopTiming.FixedUpdate) + { + switch (timeKind) + { + case TimeKind.Time: + return Time.fixedDeltaTime; + case TimeKind.UnscaledTime: + return Time.fixedUnscaledDeltaTime; + default: + break; + } + } + else + { + switch (timeKind) + { + case TimeKind.Time: + return Time.deltaTime; + case TimeKind.UnscaledTime: + return Time.unscaledDeltaTime; + default: + break; + } + } + + // DelayType.Realtime + var current = TimeProvider.System.GetTimestamp(); + var elapsed = TimeProvider.System.GetElapsedTime(lastTimestamp, current); + lastTimestamp = current; + return (float)elapsed.TotalSeconds; + } + + bool IFrameRunnerWorkItem.MoveNext(long frameCount) + { + if (isDisposed) return false; + + RunningState runState; + TimeSpan p; // period + TimeSpan d; // dueTime + lock (gate) + { + runState = runningState; + + if (runState == RunningState.ChangeRequested) + { + elapsed = 0; + if (dueTime == Timeout.InfiniteTimeSpan) + { + runningState = RunningState.Stop; + return false; + } + + runState = runningState = RunningState.RunningDueTime; + } + p = period; + d = dueTime; + } + + elapsed += GetDeltaTime(); + + try + { + if (runState == RunningState.RunningDueTime) + { + var dt = (float)d.TotalSeconds; + if (elapsed >= dt) + { + callback(state); + + elapsed = 0; + if (period == Timeout.InfiniteTimeSpan) + { + return ChangeState(RunningState.Stop); + } + else + { + return ChangeState(RunningState.RunningPeriod); + } + } + else + { + return true; + } + } + else + { + var dt = (float)p.TotalSeconds; + if (elapsed >= dt) + { + callback(state); + elapsed = 0; + } + + return ChangeState(RunningState.RunningPeriod); + } + } + catch (Exception ex) + { + ObservableSystem.GetUnhandledExceptionHandler().Invoke(ex); + return ChangeState(RunningState.Stop); + } + } + + bool ChangeState(RunningState state) + { + lock (gate) + { + // change requested is high priority + if (runningState == RunningState.ChangeRequested) + { + return true; + } + + switch (state) + { + case RunningState.RunningPeriod: + runningState = state; + return true; + default: // otherwise(Stop) + runningState = state; + return false; + } + } + } + + public void Dispose() + { + Change(Timeout.InfiniteTimeSpan, Timeout.InfiniteTimeSpan); + isDisposed = true; + } + + public ValueTask DisposeAsync() + { + Dispose(); + return default; + } + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityTimeProvider.cs.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityTimeProvider.cs.meta new file mode 100644 index 00000000..d07d83cd --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityTimeProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f17971c716f43e4bb0ef1bcdf690956 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityUIComponentExtensions.cs b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityUIComponentExtensions.cs new file mode 100644 index 00000000..d6b0756a --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityUIComponentExtensions.cs @@ -0,0 +1,103 @@ +using R3; +using System; +using UnityEngine; +using UnityEngine.UI; + +namespace UniRx +{ + public static partial class UnityUIComponentExtensions + { + public static IDisposable SubscribeToText(this Observable source, Text text) + { + return source.Subscribe(text, static (x, t) => t.text = x); + } + + public static IDisposable SubscribeToText(this Observable source, Text text) + { + return source.Subscribe(text, static (x, t) => t.text = x.ToString()); + } + + public static IDisposable SubscribeToText(this Observable source, Text text, Func selector) + { + return source.Subscribe((text, selector), static (x, state) => state.text.text = state.selector(x)); + } + + public static IDisposable SubscribeToInteractable(this Observable source, Selectable selectable) + { + return source.Subscribe(selectable, static (x, s) => s.interactable = x); + } + + /// Observe onClick event. + public static Observable OnClickAsObservable(this Button button) + { + return button.onClick.AsObservable(button.GetDestroyCancellationToken()); + } + + /// Observe onValueChanged with current `isOn` value on subscribe. + public static Observable OnValueChangedAsObservable(this Toggle toggle) + { + // Optimized Defer + StartWith + return Observable.Create(toggle, static (observer, t) => + { + observer.OnNext(t.isOn); + return t.onValueChanged.AsObservable(t.GetDestroyCancellationToken()).Subscribe(observer); + }); + } + + /// Observe onValueChanged with current `value` on subscribe. + public static Observable OnValueChangedAsObservable(this Scrollbar scrollbar) + { + return Observable.Create(scrollbar, static (observer, s) => + { + observer.OnNext(s.value); + return s.onValueChanged.AsObservable(s.GetDestroyCancellationToken()).Subscribe(observer); + }); + } + + /// Observe onValueChanged with current `normalizedPosition` value on subscribe. + public static Observable OnValueChangedAsObservable(this ScrollRect scrollRect) + { + return Observable.Create(scrollRect, static (observer, s) => + { + observer.OnNext(s.normalizedPosition); + return s.onValueChanged.AsObservable(s.GetDestroyCancellationToken()).Subscribe(observer); + }); + } + + /// Observe onValueChanged with current `value` on subscribe. + public static Observable OnValueChangedAsObservable(this Slider slider) + { + return Observable.Create(slider, static (observer, s) => + { + observer.OnNext(s.value); + return s.onValueChanged.AsObservable(s.GetDestroyCancellationToken()).Subscribe(observer); + }); + } + + /// Observe onEndEdit(Submit) event. + public static Observable OnEndEditAsObservable(this InputField inputField) + { + return inputField.onEndEdit.AsObservable(inputField.GetDestroyCancellationToken()); + } + + /// Observe onValueChanged with current `text` value on subscribe. + public static Observable OnValueChangedAsObservable(this InputField inputField) + { + return Observable.Create(inputField, static (observer, i) => + { + observer.OnNext(i.text); + return i.onValueChanged.AsObservable(i.GetDestroyCancellationToken()).Subscribe(observer); + }); + } + + /// Observe onValueChanged with current `value` on subscribe. + public static Observable OnValueChangedAsObservable(this Dropdown dropdown) + { + return Observable.Create(dropdown, static (observer, d) => + { + observer.OnNext(d.value); + return d.onValueChanged.AsObservable(d.GetDestroyCancellationToken()).Subscribe(observer); + }); + } + } +} diff --git a/src/R3.Unity/Assets/R3.Unity/Runtime/UnityUIComponentExtensions.cs.meta b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityUIComponentExtensions.cs.meta new file mode 100644 index 00000000..8e4c1fb8 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/Runtime/UnityUIComponentExtensions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 882637b17a532954791545bbc03d97cd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/R3.Unity/package.json b/src/R3.Unity/Assets/R3.Unity/package.json new file mode 100644 index 00000000..3f971c3d --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/package.json @@ -0,0 +1,12 @@ +{ + "name": "com.cysharp.R3", + "displayName": "R3", + "author": { "name": "Cysharp, Inc.", "url": "https://cysharp.co.jp/en/" }, + "version": "1.0.0", + "unity": "2021.3", + "description": "Reactive Extensions for Unity.", + "keywords": [ "rx", "event", "Scripting" ], + "license": "MIT", + "category": "Scripting", + "dependencies": {} +} diff --git a/src/R3.Unity/Assets/R3.Unity/package.json.meta b/src/R3.Unity/Assets/R3.Unity/package.json.meta new file mode 100644 index 00000000..672d5ef5 --- /dev/null +++ b/src/R3.Unity/Assets/R3.Unity/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ab73519690403a9428c7d05e008ca466 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/Scenes/NewBehaviourScript.cs b/src/R3.Unity/Assets/Scenes/NewBehaviourScript.cs new file mode 100644 index 00000000..a15318c3 --- /dev/null +++ b/src/R3.Unity/Assets/Scenes/NewBehaviourScript.cs @@ -0,0 +1,41 @@ +using R3; +using System; +using UnityEngine; + +public class NewBehaviourScript : MonoBehaviour +{ + IDisposable d; + + void Start() + { + var a = Observable.TimerFrame(5, 100) + .TakeUntil(this.destroyCancellationToken) + .Subscribe(x => + { + Debug.Log(Time.time); + }); + + + + var b = Observable.EveryUpdate() + .Where(x => true) + .Subscribe(x => + { + Debug.Log(Time.frameCount); + }); + + var c = Observable.EveryValueChanged(this, x => x.transform, destroyCancellationToken) + .Select(x => x) + .Subscribe(x => + { + Debug.Log(x); + }); + + d = Disposable.Combine(a, b, c); + } + + void OnDestroy() + { + d.Dispose(); + } +} diff --git a/src/R3.Unity/Assets/Scenes/NewBehaviourScript.cs.meta b/src/R3.Unity/Assets/Scenes/NewBehaviourScript.cs.meta new file mode 100644 index 00000000..8a40d061 --- /dev/null +++ b/src/R3.Unity/Assets/Scenes/NewBehaviourScript.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 166e09caa7feabc45bc1122fd021c625 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/R3.Unity/Assets/Scenes/SampleScene.unity b/src/R3.Unity/Assets/Scenes/SampleScene.unity index 9421266f..33141282 100644 --- a/src/R3.Unity/Assets/Scenes/SampleScene.unity +++ b/src/R3.Unity/Assets/Scenes/SampleScene.unity @@ -134,6 +134,7 @@ GameObject: - component: {fileID: 519420032} - component: {fileID: 519420031} - component: {fileID: 519420029} + - component: {fileID: 519420033} m_Layer: 0 m_Name: Main Camera m_TagString: MainCamera @@ -202,7 +203,20 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &519420033 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 166e09caa7feabc45bc1122fd021c625, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/src/R3.Unity/ProjectSettings/ProjectVersion.txt b/src/R3.Unity/ProjectSettings/ProjectVersion.txt index 8ea1b855..0ab53b0c 100644 --- a/src/R3.Unity/ProjectSettings/ProjectVersion.txt +++ b/src/R3.Unity/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2021.3.11f1 -m_EditorVersionWithRevision: 2021.3.11f1 (0a5ca18544bf) +m_EditorVersion: 2022.3.16f1 +m_EditorVersionWithRevision: 2022.3.16f1 (d2c21f0ef2f1) diff --git a/src/R3.Unity/ProjectSettings/SceneTemplateSettings.json b/src/R3.Unity/ProjectSettings/SceneTemplateSettings.json new file mode 100644 index 00000000..6f3e60fd --- /dev/null +++ b/src/R3.Unity/ProjectSettings/SceneTemplateSettings.json @@ -0,0 +1,167 @@ +{ + "templatePinStates": [], + "dependencyTypeInfos": [ + { + "userAdded": false, + "type": "UnityEngine.AnimationClip", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.Animations.AnimatorController", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.AnimatorOverrideController", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.Audio.AudioMixerController", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.ComputeShader", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Cubemap", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.GameObject", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.LightingDataAsset", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": false + }, + { + "userAdded": false, + "type": "UnityEngine.LightingSettings", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Material", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.MonoScript", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicMaterial", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.PhysicsMaterial2D", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Rendering.VolumeProfile", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEditor.SceneAsset", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": false + }, + { + "userAdded": false, + "type": "UnityEngine.Shader", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.ShaderVariantCollection", + "ignore": true, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Texture", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Texture2D", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + }, + { + "userAdded": false, + "type": "UnityEngine.Timeline.TimelineAsset", + "ignore": false, + "defaultInstantiationMode": 0, + "supportsModification": true + } + ], + "defaultDependencyTypeInfo": { + "userAdded": false, + "type": "", + "ignore": false, + "defaultInstantiationMode": 1, + "supportsModification": true + }, + "newSceneOverride": 0 +} \ No newline at end of file diff --git a/src/R3.Unity/ProjectSettings/boot.config b/src/R3.Unity/ProjectSettings/boot.config deleted file mode 100644 index e69de29b..00000000 diff --git a/src/R3.WPF/WpfRenderingFrameProvider.cs b/src/R3.WPF/WpfRenderingFrameProvider.cs index 0e90be6a..a6a708ae 100644 --- a/src/R3.WPF/WpfRenderingFrameProvider.cs +++ b/src/R3.WPF/WpfRenderingFrameProvider.cs @@ -3,7 +3,7 @@ namespace R3.WPF; -public sealed class WpfRenderingFrameProvider : FrameProvider +public sealed class WpfRenderingFrameProvider : FrameProvider, IDisposable { bool disposed; long frameCount; diff --git a/src/R3/Collections/FreeListCore.cs b/src/R3/Collections/FreeListCore.cs index c0b6f878..fa9b10b6 100644 --- a/src/R3/Collections/FreeListCore.cs +++ b/src/R3/Collections/FreeListCore.cs @@ -85,6 +85,7 @@ public bool RemoveSlow(T value) lock (gate) { if (values == null) return false; + if (lastIndex < 0) return false; var index = -1; var span = values.AsSpan(0, lastIndex); @@ -110,7 +111,10 @@ public void Clear(bool removeArray) { lock (gate) { - values.AsSpan(0, lastIndex).Clear(); + if (lastIndex > 0) + { + values.AsSpan(0, lastIndex).Clear(); + } if (removeArray) { values = null; diff --git a/src/R3/SubscriptionTracker.cs b/src/R3/SubscriptionTracker.cs index 860ab5e3..05a2acec 100644 --- a/src/R3/SubscriptionTracker.cs +++ b/src/R3/SubscriptionTracker.cs @@ -9,11 +9,8 @@ public static class SubscriptionTracker { static int trackingIdCounter = 0; - // TODO: UnityEditor? AppSwitch? - // public static bool EnableAutoReload = false; public static bool EnableTracking = false; public static bool EnableStackTrace = false; - // TODO: EnableStackTraceFileLink static readonly WeakDictionary tracking = new();