From 76c2d243f0e7a3f21ea4a0efc576de09d71667ca Mon Sep 17 00:00:00 2001 From: amylizzlep Date: Mon, 18 Nov 2024 00:07:26 +0000 Subject: [PATCH 1/7] code reload --- OpenDreamRuntime/DreamManager.cs | 23 +++++++++++++++++++ .../Procs/DebugAdapter/DreamDebugManager.cs | 23 ++++++++++++++++++- .../Procs/DebugAdapter/Protocol/Request.cs | 1 + .../Protocol/RequestHotReloadBytecode.cs | 18 +++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 OpenDreamRuntime/Procs/DebugAdapter/Protocol/RequestHotReloadBytecode.cs diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index 20aacd3428..f110b597e2 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -117,6 +117,29 @@ public void ProcessDelQueue() { } } + public void HotReloadJson(string? jsonPath) { + if (string.IsNullOrEmpty(jsonPath) || !File.Exists(jsonPath)) + throw new FileNotFoundException("Could not find the specified json file"); + + string jsonSource = File.ReadAllText(jsonPath); + DreamCompiledJson? json = JsonSerializer.Deserialize(jsonSource); + if (json == null) + throw new Exception("Failed to deserialize the json file"); + + if (!json.Metadata.Version.Equals(OpcodeVerifier.GetOpcodesHash())) + throw new Exception("Compiler opcode version does not match the runtime version!"); + + _compiledJson = json; + var rootPath = Path.GetFullPath(Path.GetDirectoryName(jsonPath)!); + var resources = _compiledJson.Resources ?? Array.Empty(); + _dreamResourceManager.Initialize(rootPath, resources); + if(!string.IsNullOrEmpty(_compiledJson.Interface) && !_dreamResourceManager.DoesFileExist(_compiledJson.Interface)) + throw new FileNotFoundException("Interface DMF not found at "+Path.Join(rootPath,_compiledJson.Interface)); + _objectTree.LoadJson(json); + DreamProcNative.SetupNativeProcs(_objectTree); + + } + public bool LoadJson(string? jsonPath) { if (string.IsNullOrEmpty(jsonPath) || !File.Exists(jsonPath)) return false; diff --git a/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs b/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs index 56339a2316..ba08dfddb2 100644 --- a/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs +++ b/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs @@ -6,6 +6,8 @@ using OpenDreamRuntime.Procs.DebugAdapter.Protocol; using OpenDreamRuntime.Resources; using Robust.Server; +using Robust.Shared.Configuration; +using OpenDreamShared; namespace OpenDreamRuntime.Procs.DebugAdapter; @@ -15,6 +17,7 @@ internal sealed class DreamDebugManager : IDreamDebugManager { [Dependency] private readonly DreamResourceManager _resourceManager = default!; [Dependency] private readonly ProcScheduler _procScheduler = default!; [Dependency] private readonly IBaseServer _server = default!; + [Dependency] private readonly IConfigurationManager _configManager = default!; private ISawmill _sawmill = default!; @@ -353,6 +356,9 @@ private void OnRequest(DebugAdapterClient client, Request req) { case RequestHotReloadResource requestHotReloadResource: HandleRequestHotReloadResource(client, requestHotReloadResource); break; + case RequestHotReloadBytecode requestHotReloadBytecode: + HandleRequestHotReloadBytecode(client, requestHotReloadBytecode); + break; default: req.RespondError(client, $"Unknown request \"{req.Command}\""); break; @@ -848,7 +854,7 @@ private void HandleRequestHotReloadResource(DebugAdapterClient client, RequestHo requestHotReloadResource.RespondError(client, "No file provided for a hot reload"); return; } - + _sawmill.Debug("Debug adapter triggered resource hot reload for "+requestHotReloadResource.Arguments.FilePath); try { _dreamManager.HotReloadResource(requestHotReloadResource.Arguments.FilePath); @@ -858,6 +864,21 @@ private void HandleRequestHotReloadResource(DebugAdapterClient client, RequestHo } } + private void HandleRequestHotReloadBytecode(DebugAdapterClient client, RequestHotReloadBytecode requestHotReloadBytecode) { + if (string.IsNullOrWhiteSpace(requestHotReloadBytecode.Arguments.FilePath)) { + _sawmill.Error("Debug adapter requested a bytecode hot reload but didn't provide a json file"); + requestHotReloadBytecode.RespondError(client, "No file provided for a hot reload"); + return; + } + _sawmill.Debug($"Debug adapter triggered bytecode hot reload for file {requestHotReloadBytecode.Arguments.FilePath}"); + try { + _dreamManager.HotReloadJson(_configManager.GetCVar(OpenDreamCVars.JsonPath)); + requestHotReloadBytecode.Respond(client); + } catch (Exception e) { + requestHotReloadBytecode.RespondError(client, e.Message); + } + } + private IEnumerable DisassemblySkipTake(List list, int midpoint, int offset, int count) { for (int i = midpoint + offset; i < midpoint + offset + count; ++i) { if (i < 0) { diff --git a/OpenDreamRuntime/Procs/DebugAdapter/Protocol/Request.cs b/OpenDreamRuntime/Procs/DebugAdapter/Protocol/Request.cs index d27c2d725b..09bc8476e6 100644 --- a/OpenDreamRuntime/Procs/DebugAdapter/Protocol/Request.cs +++ b/OpenDreamRuntime/Procs/DebugAdapter/Protocol/Request.cs @@ -37,6 +37,7 @@ public Request() : base("request") { } "disassemble" => json.Deserialize(), "hotreloadinterface" => json.Deserialize(), "hotreloadresource" => json.Deserialize(), + "hotreloadbytecode" => json.Deserialize(), // Caller will fail to recognize it and can respond with `success: false`. _ => request, }; diff --git a/OpenDreamRuntime/Procs/DebugAdapter/Protocol/RequestHotReloadBytecode.cs b/OpenDreamRuntime/Procs/DebugAdapter/Protocol/RequestHotReloadBytecode.cs new file mode 100644 index 0000000000..05ed0d1c16 --- /dev/null +++ b/OpenDreamRuntime/Procs/DebugAdapter/Protocol/RequestHotReloadBytecode.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; +using JetBrains.Annotations; + +namespace OpenDreamRuntime.Procs.DebugAdapter.Protocol; + +[UsedImplicitly] +public sealed class RequestHotReloadBytecode : Request { + [JsonPropertyName("arguments")] public required RequestHotReloadBytecodeArguments Arguments { get; set; } + + [UsedImplicitly] + public sealed class RequestHotReloadBytecodeArguments { + [JsonPropertyName("file")] public string? FilePath { get; set; } + } + + public void Respond(DebugAdapterClient client) { + client.SendMessage(Response.NewSuccess(this)); + } +} From 6fac16aed77a534b56e5341726807de19c3f4402 Mon Sep 17 00:00:00 2001 From: amylizzlep Date: Mon, 18 Nov 2024 23:27:41 +0000 Subject: [PATCH 2/7] update object defs on active objects --- OpenDreamRuntime/DreamManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index f110b597e2..706e0b926d 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -138,6 +138,10 @@ public void HotReloadJson(string? jsonPath) { _objectTree.LoadJson(json); DreamProcNative.SetupNativeProcs(_objectTree); + foreach(DreamObject dreamObject in IterateDatums()){ + dreamObject.ObjectDefinition = _objectTree.GetObjectDefinition(_objectTree.GetTreeEntry(dreamObject.ObjectDefinition.Type).Id); + } + } public bool LoadJson(string? jsonPath) { From 0ae999ea2c7967b2d1b1943294426562b6df520a Mon Sep 17 00:00:00 2001 From: amylizzle Date: Tue, 19 Nov 2024 14:09:48 +0000 Subject: [PATCH 3/7] add command --- OpenDreamRuntime/DreamManager.Connections.cs | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/OpenDreamRuntime/DreamManager.Connections.cs b/OpenDreamRuntime/DreamManager.Connections.cs index c3c6929016..c61023dbc5 100644 --- a/OpenDreamRuntime/DreamManager.Connections.cs +++ b/OpenDreamRuntime/DreamManager.Connections.cs @@ -358,3 +358,27 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) { dreamManager.HotReloadResource(args[0]); } } + +public sealed class HotReloadCodeCommand : IConsoleCommand { + // ReSharper disable once StringLiteralTypo + public string Command => "hotreloadcode"; + public string Description => "Reload a specified compiled json and hot reload the bytecode"; + public string Help => ""; + public bool RequireServerOrSingleplayer => true; + + public void Execute(IConsoleShell shell, string argStr, string[] args) { + if(!shell.IsLocal) { + shell.WriteError("You cannot use this command as a client. Execute it on the server console."); + return; + } + + if (args.Length != 1) { + shell.WriteError("This command requires a file path to reload as an argument! Example: hotreloadresource ./path/to/compiled.json"); + return; + } + + DreamManager dreamManager = IoCManager.Resolve(); + shell.WriteLine($"Reloading {args[0]}"); + dreamManager.HotReloadJson(args[0]); + } +} From dcfa825e89b02d500518c09849501452640215ad Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 3 Jul 2025 22:38:43 +0100 Subject: [PATCH 4/7] make it compile --- OpenDreamRuntime/DreamManager.Connections.cs | 2 +- OpenDreamRuntime/DreamManager.cs | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/OpenDreamRuntime/DreamManager.Connections.cs b/OpenDreamRuntime/DreamManager.Connections.cs index 69c3486fbd..e9e26a414d 100644 --- a/OpenDreamRuntime/DreamManager.Connections.cs +++ b/OpenDreamRuntime/DreamManager.Connections.cs @@ -371,7 +371,7 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) { } if (args.Length != 1) { - shell.WriteError("This command requires a file path to reload as an argument! Example: hotreloadresource ./path/to/compiled.json"); + shell.WriteError("This command requires a file path to reload as an argument! Example: hotreloadcode ./path/to/compiled.json"); return; } diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index 08af510728..90873f1330 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -151,7 +151,7 @@ public void ProcessDelQueue() { public bool TryGetGlobalProc(string name, [NotNullWhen(true)] out DreamProc? proc) { return _objectTree.TryGetGlobalProc(name, out proc); } - + public void HotReloadJson(string? jsonPath) { if (string.IsNullOrEmpty(jsonPath) || !File.Exists(jsonPath)) throw new FileNotFoundException("Could not find the specified json file"); @@ -164,12 +164,9 @@ public void HotReloadJson(string? jsonPath) { if (!json.Metadata.Version.Equals(OpcodeVerifier.GetOpcodesHash())) throw new Exception("Compiler opcode version does not match the runtime version!"); - _compiledJson = json; var rootPath = Path.GetFullPath(Path.GetDirectoryName(jsonPath)!); - var resources = _compiledJson.Resources ?? Array.Empty(); - _dreamResourceManager.Initialize(rootPath, resources); - if(!string.IsNullOrEmpty(_compiledJson.Interface) && !_dreamResourceManager.DoesFileExist(_compiledJson.Interface)) - throw new FileNotFoundException("Interface DMF not found at "+Path.Join(rootPath,_compiledJson.Interface)); + var resources = json.Resources ?? Array.Empty(); + _dreamResourceManager.Initialize(rootPath, resources, json.Interface); _objectTree.LoadJson(json); DreamProcNative.SetupNativeProcs(_objectTree); From 8516d4563d94b19fa6c0eb024a481679dc68e9b2 Mon Sep 17 00:00:00 2001 From: amy Date: Fri, 4 Jul 2025 00:01:04 +0100 Subject: [PATCH 5/7] the verbs resist change, but change they must --- OpenDreamClient/ClientVerbSystem.cs | 2 +- OpenDreamRuntime/DreamManager.cs | 10 +++++++++- OpenDreamRuntime/ServerVerbSystem.cs | 9 +++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/OpenDreamClient/ClientVerbSystem.cs b/OpenDreamClient/ClientVerbSystem.cs index 7927fdaf41..a3633948f9 100644 --- a/OpenDreamClient/ClientVerbSystem.cs +++ b/OpenDreamClient/ClientVerbSystem.cs @@ -226,7 +226,7 @@ private void OnAllVerbsEvent(AllVerbsEvent e) { } private void OnRegisterVerbEvent(RegisterVerbEvent e) { - _verbs.Add(e.VerbId, e.VerbInfo); + _verbs[e.VerbId]= e.VerbInfo; } private void OnUpdateClientVerbsEvent(UpdateClientVerbsEvent e) { diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index 90873f1330..4bca521e44 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -167,13 +167,21 @@ public void HotReloadJson(string? jsonPath) { var rootPath = Path.GetFullPath(Path.GetDirectoryName(jsonPath)!); var resources = json.Resources ?? Array.Empty(); _dreamResourceManager.Initialize(rootPath, resources, json.Interface); + if (_entitySystemManager.TryGetEntitySystem(out ServerVerbSystem? _verbSystem)) + _verbSystem.ClearAllVerbs(); _objectTree.LoadJson(json); DreamProcNative.SetupNativeProcs(_objectTree); - foreach(DreamObject dreamObject in IterateDatums()){ + foreach (DreamObject dreamObject in IterateDatums()) { dreamObject.ObjectDefinition = _objectTree.GetObjectDefinition(_objectTree.GetTreeEntry(dreamObject.ObjectDefinition.Type).Id); } + foreach (var client in Connections) { + if(client.Client is not null) + _verbSystem?.UpdateClientVerbs(client.Client); + } + + } public bool LoadJson(string? jsonPath) { diff --git a/OpenDreamRuntime/ServerVerbSystem.cs b/OpenDreamRuntime/ServerVerbSystem.cs index 6da5de48e6..e1f89aa9fb 100644 --- a/OpenDreamRuntime/ServerVerbSystem.cs +++ b/OpenDreamRuntime/ServerVerbSystem.cs @@ -201,4 +201,13 @@ private bool CanExecute(DreamConnection connection, DreamObject src, DreamProc v return true; } } + + /// + /// Clear all registered verbs. Does not update clients. Really don't use this unless you are immediately + /// repopulating the verb list. + /// + public void ClearAllVerbs() { + _verbs.Clear(); + _verbIdToProc.Clear(); + } } From 4f5f66e3b4b875f11dd7ea34cdeb81435f28361b Mon Sep 17 00:00:00 2001 From: amy Date: Sat, 12 Jul 2025 14:32:57 +0100 Subject: [PATCH 6/7] closer --- OpenDreamClient/ClientVerbSystem.cs | 1 + OpenDreamRuntime/DreamManager.cs | 7 +++++-- OpenDreamRuntime/Objects/Types/DreamList.cs | 15 +++++++++++++++ OpenDreamRuntime/ServerVerbSystem.cs | 4 ++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/OpenDreamClient/ClientVerbSystem.cs b/OpenDreamClient/ClientVerbSystem.cs index a3633948f9..ee4913d90c 100644 --- a/OpenDreamClient/ClientVerbSystem.cs +++ b/OpenDreamClient/ClientVerbSystem.cs @@ -214,6 +214,7 @@ is VerbAccessibility.InRange or VerbAccessibility.InORange } private void OnAllVerbsEvent(AllVerbsEvent e) { + _verbs.Clear(); _verbs.EnsureCapacity(e.Verbs.Count); for (int i = 0; i < e.Verbs.Count; i++) { diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index 4bca521e44..4252eb642c 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -169,6 +169,7 @@ public void HotReloadJson(string? jsonPath) { _dreamResourceManager.Initialize(rootPath, resources, json.Interface); if (_entitySystemManager.TryGetEntitySystem(out ServerVerbSystem? _verbSystem)) _verbSystem.ClearAllVerbs(); + _objectTree.LoadJson(json); DreamProcNative.SetupNativeProcs(_objectTree); @@ -177,8 +178,10 @@ public void HotReloadJson(string? jsonPath) { } foreach (var client in Connections) { - if(client.Client is not null) - _verbSystem?.UpdateClientVerbs(client.Client); + if (client.Client is not null) { + client.Client.ClientVerbs.HotReloadAll(_objectTree); + _verbSystem!.UpdateClientVerbs(client.Client); + } } diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs index 9eff66d1d9..1d7316433a 100644 --- a/OpenDreamRuntime/Objects/Types/DreamList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamList.cs @@ -613,6 +613,21 @@ public ClientVerbsList(DreamObjectTree objectTree, ServerVerbSystem? verbSystem, } } + public void HotReloadAll(DreamObjectTree objectTree) { + Verbs.Clear(); + + List? verbs = _client.ObjectDefinition.Verbs; + if (verbs == null) + return; + + Verbs.EnsureCapacity(verbs.Count); + foreach (int verbId in verbs) { + Verbs.Add(objectTree.Procs[verbId]); + } + + _verbSystem!.HotReloadAllVerbs(_client); + } + public override DreamValue GetValue(DreamValue key) { if (!key.TryGetValueAsInteger(out var index)) throw new Exception($"Invalid index into verbs list: {key}"); diff --git a/OpenDreamRuntime/ServerVerbSystem.cs b/OpenDreamRuntime/ServerVerbSystem.cs index e1f89aa9fb..82514c81e8 100644 --- a/OpenDreamRuntime/ServerVerbSystem.cs +++ b/OpenDreamRuntime/ServerVerbSystem.cs @@ -122,6 +122,10 @@ public void UpdateClientVerbs(DreamObjectClient client) { RaiseNetworkEvent(new UpdateClientVerbsEvent(verbIds), client.Connection.Session!); } + public void HotReloadAllVerbs(DreamObjectClient client) { + RaiseNetworkEvent(new AllVerbsEvent(_verbs), client.Connection.Session!); + } + private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { if (e.NewStatus != SessionStatus.InGame) return; From 4e465b0b4d12ffdeb9b1193b7b4f05899908cdd0 Mon Sep 17 00:00:00 2001 From: amy Date: Sat, 12 Jul 2025 15:25:21 +0100 Subject: [PATCH 7/7] check if removing verbs works on master --- OpenDreamClient/ClientVerbSystem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenDreamClient/ClientVerbSystem.cs b/OpenDreamClient/ClientVerbSystem.cs index ee4913d90c..808cf3ce7e 100644 --- a/OpenDreamClient/ClientVerbSystem.cs +++ b/OpenDreamClient/ClientVerbSystem.cs @@ -215,6 +215,7 @@ is VerbAccessibility.InRange or VerbAccessibility.InORange private void OnAllVerbsEvent(AllVerbsEvent e) { _verbs.Clear(); + _clientVerbs?.Clear(); _verbs.EnsureCapacity(e.Verbs.Count); for (int i = 0; i < e.Verbs.Count; i++) {