diff --git a/EnhancedUI/Content/ControlPanel.js b/EnhancedUI/Content/ControlPanel.js index d061e4c..e3d8ae7 100644 --- a/EnhancedUI/Content/ControlPanel.js +++ b/EnhancedUI/Content/ControlPanel.js @@ -5,7 +5,7 @@ let rendering = false; // noinspection JSUnusedGlobalSymbols async function OnGameStateChange(version) { // Is the model accessible? - if (TerminalViewModel === undefined) + if (typeof TerminalViewModel === typeof undefined) return; // Eliminate any any duplicate or redundant calls @@ -123,7 +123,7 @@ function renderBlockProperty(parent, blockId, propertyState) { value.append(input); value.bind('change', async function (e) { - await TerminalViewModel.SetBlockProperty(blockId, propertyId, parseInt(input.value())); + await TerminalViewModel.SetBlockProperty(blockId, propertyId, parseInt(input.val())); }); break; @@ -143,7 +143,7 @@ function renderBlockProperty(parent, blockId, propertyState) { value.append(input); value.bind('change', async function (e) { - await TerminalViewModel.SetBlockProperty(blockId, propertyId, parseFloat(input.value())); + await TerminalViewModel.SetBlockProperty(blockId, propertyId, parseFloat(input.val())); }); break; @@ -162,7 +162,7 @@ function renderBlockProperty(parent, blockId, propertyState) { value.append(input); value.bind('change', async function (e) { - await TerminalViewModel.SetBlockProperty(blockId, propertyId, input.value()); + await TerminalViewModel.SetBlockProperty(blockId, propertyId, input.val()); }); break; @@ -183,7 +183,7 @@ function renderBlockProperty(parent, blockId, propertyState) { value.append(input); value.bind('change', async function (e) { - await TerminalViewModel.SetBlockProperty(blockId, propertyId, input.value()); + await TerminalViewModel.SetBlockProperty(blockId, propertyId, input.val()); }); break; diff --git a/EnhancedUI/Gui/ChromiumGuiControl.cs b/EnhancedUI/Gui/ChromiumGuiControl.cs index 84d28e8..a107f45 100644 --- a/EnhancedUI/Gui/ChromiumGuiControl.cs +++ b/EnhancedUI/Gui/ChromiumGuiControl.cs @@ -154,7 +154,7 @@ private void OnGameStateChanged(long version) if (!IsBrowserInitialized) return; - chromium?.Browser.ExecuteScriptAsync($"OnGameStateChange({version})"); + chromium?.Browser.ExecuteScriptAsync($"if (typeof OnGameStateChange != typeof undefined) OnGameStateChange({version})"); } private void Navigate() diff --git a/EnhancedUI/Gui/Terminal/ControlPanel/MyGuiScreenTerminal_Patch.cs b/EnhancedUI/Gui/Terminal/ControlPanel/MyGuiScreenTerminal_Patch.cs index 0079b60..5990e4a 100644 --- a/EnhancedUI/Gui/Terminal/ControlPanel/MyGuiScreenTerminal_Patch.cs +++ b/EnhancedUI/Gui/Terminal/ControlPanel/MyGuiScreenTerminal_Patch.cs @@ -42,9 +42,19 @@ private static bool CreateControlPanelPageControlsPrefix( // Focus the browser "control" by default when the tab is selected (see the original function) ___m_defaultFocusedControlKeyboard[MyTerminalPageEnum.ControlPanel] = control; - ___m_defaultFocusedControlGamepad[MyTerminalPageEnum.ControlPanel] = control; + + // FIXME: Looks like this is not needed, since it does not present in the original code either. + //___m_defaultFocusedControlGamepad[MyTerminalPageEnum.ControlPanel] = control; return false; } + + [HarmonyPatch("AttachGroups")] + [HarmonyPrefix] + private static bool AttachGroupsPatch() => false; + + [HarmonyPatch("DetachGroups")] + [HarmonyPrefix] + private static bool DetachGroupsPatch() => false; } } \ No newline at end of file diff --git a/EnhancedUI/Gui/Terminal/ControlPanel/MyTerminalControlPanel_Close_Patch.cs b/EnhancedUI/Gui/Terminal/ControlPanel/MyTerminalControlPanel_Close_Patch.cs index 2681f8f..602e7d9 100644 --- a/EnhancedUI/Gui/Terminal/ControlPanel/MyTerminalControlPanel_Close_Patch.cs +++ b/EnhancedUI/Gui/Terminal/ControlPanel/MyTerminalControlPanel_Close_Patch.cs @@ -1,6 +1,5 @@ using System; using System.Reflection; -using EnhancedUI.ViewModel; using HarmonyLib; namespace EnhancedUI.Gui.Terminal.ControlPanel diff --git a/EnhancedUI/ViewModel/BlockViewModel.cs b/EnhancedUI/ViewModel/BlockViewModel.cs index de827af..b290f59 100644 --- a/EnhancedUI/ViewModel/BlockViewModel.cs +++ b/EnhancedUI/ViewModel/BlockViewModel.cs @@ -59,9 +59,11 @@ public class BlockViewModel : IDisposable // ReSharper disable once UnusedMember.Global // ReSharper disable once MemberCanBePrivate.Global public string ClassName => block.GetType().Name; + // ReSharper disable once UnusedMember.Global // ReSharper disable once MemberCanBePrivate.Global public string TypeId => block.BlockDefinition.Id.TypeId.ToString(); + // ReSharper disable once UnusedMember.Global // ReSharper disable once MemberCanBePrivate.Global public string SubtypeName => block.BlockDefinition.Id.SubtypeName; @@ -69,6 +71,7 @@ public class BlockViewModel : IDisposable // Geometry // ReSharper disable once UnusedMember.Global public int[] Position => block.Position.ToArray(); + // ReSharper disable once UnusedMember.Global public int[] Size => block.BlockDefinition.Size.ToArray(); @@ -85,17 +88,15 @@ public BlockViewModel(TerminalViewModel terminalViewModel, MyTerminalBlock termi MyLog.Default.Info($"EnhancedUI: {this}"); - UpdateFields(version); + UpdateFields(true); CreatePropertyModels(); block.PropertiesChanged += OnPropertyChanged; } #pragma warning restore 8618 - private bool UpdateFields(long version) + private void UpdateFields(bool changed = false) { - var changed = false; - var isValid = !block.Closed && block.InScene && !block.IsPreview; if (isValid != IsValid) { @@ -133,9 +134,7 @@ private bool UpdateFields(long version) } if (changed) - Version = version; - - return changed; + Version = terminalModel.GetNextVersion(); } private void CreatePropertyModels() @@ -161,50 +160,39 @@ private void OnPropertyChanged(MyTerminalBlock obj) } // Updates model from game state, returns true if anything has changed - public bool Update(long version) + public void Update() { - var changed = UpdateFields(version); - return UpdateProperties(version) || changed; + UpdateFields(); + UpdateProperties(); } - private bool UpdateProperties(long version) + private void UpdateProperties() { var changed = false; foreach (var property in Properties.Values) changed = property.Update(block) || changed; if (changed) - Version = version; - - return changed; + Version = terminalModel.GetNextVersion(); } // Applies model changes to game state, returns true if anything has changed - public bool Apply(long version) + public void Apply() { - var changed = false; - var defaultName = block.DisplayNameText ?? block.DisplayName; if (Name != defaultName && Name != block.CustomName.ToString()) { block.CustomName.Clear(); block.CustomName.Append(Name); - changed = true; } if (CustomData != block.CustomData.ToString()) { block.CustomData = CustomData; - changed = true; } foreach (var property in Properties.Values) - changed = property.Apply(block) || changed; - - if (changed) - Version = version; - - return changed; + property.Apply(block); } public void SetName(string name) @@ -230,6 +218,7 @@ public void SetProperty(string propertyId, object? value) private void NotifyChange() { + Version = terminalModel.GetNextVersion(); terminalModel.NotifyUserModifiedBlock(Id); } } diff --git a/EnhancedUI/ViewModel/ITerminalViewModel.cs b/EnhancedUI/ViewModel/ITerminalViewModel.cs index a5c552a..ebfe283 100644 --- a/EnhancedUI/ViewModel/ITerminalViewModel.cs +++ b/EnhancedUI/ViewModel/ITerminalViewModel.cs @@ -6,7 +6,10 @@ namespace EnhancedUI.ViewModel public interface ITerminalViewModel { // TODO: Grid API - // TODO: Provide a way to query the ID of the interacted block. + + // Returns the ID of the interacted block the player is directly connected to. + // Returns null if the player is not connected to a terminal port. + long? GetInteractedBlockId(); // Returns list of IDs of blocks the player have access to via the interacted block. // Returns empty list if the player is not connected to a terminal port. @@ -25,7 +28,7 @@ public interface ITerminalViewModel // Modifies a block's CustomData, actual modification will happen on the next game update void SetBlockCustomData(long blockId, string customData); - // Modified a property value, actual modification will happen on the next game update + // Modifies a block property's value, actual modification will happen on the next game update void SetBlockProperty(long blockId, string propertyId, object? value); // Returns the named groups of blocks the player have access to via the interacted block. diff --git a/EnhancedUI/ViewModel/PropertyViewModel.cs b/EnhancedUI/ViewModel/PropertyViewModel.cs index a310237..af0ed47 100644 --- a/EnhancedUI/ViewModel/PropertyViewModel.cs +++ b/EnhancedUI/ViewModel/PropertyViewModel.cs @@ -40,9 +40,9 @@ public bool Update(MyTerminalBlock block) } // Applies the value to the in-game property - public bool Apply(MyTerminalBlock block) + public void Apply(MyTerminalBlock block) { - return Write(block, property, Value); + Write(block, property, Value); } private static object? Read(MyTerminalBlock block, ITerminalProperty property) @@ -68,41 +68,40 @@ public bool Apply(MyTerminalBlock block) return null; } - private static bool Write(MyTerminalBlock block, ITerminalProperty property, object? value) + private static void Write(MyTerminalBlock block, ITerminalProperty property, object? value) { - if (value == Read(block, property)) - return false; + var currentValue = Read(block, property); + if (value == currentValue) + return; switch (property.TypeName) { case "Boolean": property.AsBool().SetValue(block, value as bool? ?? property.AsBool().GetDefaultValue(block)); - return true; + break; case "Single": property.AsFloat().SetValue(block, value as float? ?? property.AsFloat().GetDefaultValue(block)); - return true; + break; case "Int64": property.As().SetValue(block, value as long? ?? property.As().GetDefaultValue(block)); - return true; + break; case "StringBuilder": property.As().SetValue(block, value == null ? property.As().GetDefaultValue(block) : new StringBuilder(value as string ?? "")); - return true; + break; case "Color": property.As().SetValue(block, value == null ? property.As().GetDefaultValue(block) : ParseColor(value as string ?? "")); - return true; + break; } - - return false; } private static string FormatColor(Color c) diff --git a/EnhancedUI/ViewModel/TerminalViewModel.cs b/EnhancedUI/ViewModel/TerminalViewModel.cs index 554b737..7f3ae3d 100644 --- a/EnhancedUI/ViewModel/TerminalViewModel.cs +++ b/EnhancedUI/ViewModel/TerminalViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using EnhancedUI.Utils; using Sandbox.Game.Entities.Cube; using VRage.Utils; @@ -12,10 +13,6 @@ public class TerminalViewModel : ITerminalViewModel, IDisposable // Model is a singleton public static TerminalViewModel? Instance; - // Logical clock, model state versions for browser synchronization - private readonly object latestVersionLock = new(); - private long latestVersion; - // Event triggered on new game state versions public delegate void OnGameStateChangedHandler(long version); @@ -33,10 +30,15 @@ public class TerminalViewModel : ITerminalViewModel, IDisposable // Terminal block the player interacts with // Set to null if the player is not connected to any grids private MyTerminalBlock? interactedBlock; + private long? interactedBlockId; // True if the player is connected to a terminal system private bool IsConnected => interactedBlock?.IsFunctional == true; + // Logical clock, model state version number for browser synchronization + private long latestVersion; + public long GetNextVersion() => Interlocked.Increment(ref latestVersion); + public TerminalViewModel() { if (Instance != null) @@ -83,6 +85,7 @@ private void Clear() blocks.Clear(); interactedBlock = null; + interactedBlockId = null; } } @@ -100,15 +103,10 @@ private void CreateBlockViewModels() { var blockViewModel = new BlockViewModel(this, block, version); blocks[blockViewModel.Id] = blockViewModel; - } - } - } - private long GetNextVersion() - { - lock (latestVersionLock) - { - return ++latestVersion; + if (block == interactedBlock) + interactedBlockId = blockViewModel.Id; + } } } @@ -133,54 +131,53 @@ internal void Update() return; } - bool changed; + ApplyUserModifications(); + + long versionBefore = latestVersion; + lock (blocks) { - var version = GetNextVersion(); - changed = UpdateGameModifiedBlocks(version); - changed = ApplyUserModifications(version) || changed; + UpdateGameModifiedBlocks(); } - if (changed) + if (latestVersion != versionBefore) { MyLog.Default.Debug($"EnhancedUI: OnGameStateChanged({latestVersion})"); OnGameStateChanged?.Invoke(latestVersion); } } - private bool ApplyUserModifications(long version) + private void ApplyUserModifications() { - var changed = false; - using var context = blocksModifiedByUser.Process(); foreach (var blockId in context.Items) { if (!blocks.TryGetValue(blockId, out var block)) continue; - changed = block.Apply(version) || changed; + block.Apply(); } - - return changed; } - private bool UpdateGameModifiedBlocks(long version) + private void UpdateGameModifiedBlocks() { using var context = blocksModifiedByGame.Process(); - var changed = false; foreach (var blockId in context.Items) { if (!blocks.TryGetValue(blockId, out var block)) continue; - changed = block.Update(version) || changed; + block.Update(); } - - return changed; } #region JavaScript API + public long? GetInteractedBlockId() + { + return interactedBlockId; + } + public List GetBlockIds() { lock (blocks)