Skip to content

Commit

Permalink
Enable hot reloading of interface files (#1779)
Browse files Browse the repository at this point in the history
  • Loading branch information
amylizzle committed Jun 9, 2024
1 parent c0c190d commit 89f5e6c
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 9 deletions.
2 changes: 2 additions & 0 deletions DMCompiler/DMStandard/Types/World.dm
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,5 @@
proc/PayCredits(player, credits, note)
set opendream_unimplemented = TRUE
return 0

proc/ODHotReloadInterface()
8 changes: 8 additions & 0 deletions OpenDreamClient/Interface/Controls/ControlWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ public sealed class ControlWindow : InterfaceControl {
}
}

/// <summary>
/// Closes the window if it is a child window. No effect if it is either a default window or a pane
/// </summary>
public void CloseChildWindow() {
if(_myWindow.osWindow is not null)
_myWindow.osWindow.Close();
}

public OSWindow CreateWindow() {
if(_myWindow.osWindow is not null)
return _myWindow.osWindow;
Expand Down
15 changes: 15 additions & 0 deletions OpenDreamClient/Interface/DreamInterfaceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ internal sealed class DreamInterfaceManager : IDreamInterfaceManager {

LoadInterfaceFromSource(interfaceText);
_netManager.ClientSendMessage(new MsgAckLoadInterface());
if (_entitySystemManager.TryGetEntitySystem(out ClientVerbSystem? verbSystem))
DefaultInfo?.RefreshVerbs(verbSystem);
}

private void RxUpdateClientInfo(MsgUpdateClientInfo msg) {
Expand Down Expand Up @@ -793,10 +795,23 @@ internal sealed class DreamInterfaceManager : IDreamInterfaceManager {
private void Reset() {
_userInterfaceManager.MainViewport.Visible = false;

//close windows if they're open, and clear all child uielements
foreach (var window in Windows.Values){
window.CloseChildWindow();
window.UIElement.RemoveAllChildren();
}

Windows.Clear();
Menus.Clear();
MacroSets.Clear();

//close popups if they're open
foreach (var popup in _popupWindows.Values) {
popup.Close();
}

_popupWindows.Clear();
_inputManager.ResetAllBindings();
}

private void LoadInterface(InterfaceDescriptor descriptor) {
Expand Down
43 changes: 42 additions & 1 deletion OpenDreamRuntime/DreamManager.Connections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using OpenDreamRuntime;
using OpenDreamShared;
using OpenDreamShared.Network.Messages;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Player;
Expand Down Expand Up @@ -223,7 +225,8 @@ public sealed partial class DreamManager {
private void RxAckLoadInterface(MsgAckLoadInterface message) {
// Once the client loaded the interface, move them to in-game.
var player = _playerManager.GetSessionByChannel(message.MsgChannel);
_playerManager.JoinGame(player);
if(player.Status != SessionStatus.InGame) //Don't rejoin if this is a hot reload of interface
_playerManager.JoinGame(player);
}

private void RxBrowseResourceRequest(MsgBrowseResourceRequest message) {
Expand Down Expand Up @@ -278,5 +281,43 @@ public sealed partial class DreamManager {
public DreamConnection GetConnectionBySession(ICommonSession session) {
return _connections[session.UserId];
}

public void HotReloadInterface() {
string? interfaceText = null;
if (_compiledJson.Interface != null)
interfaceText = _dreamResourceManager.LoadResource(_compiledJson.Interface, forceReload:true).ReadAsString();

var msgLoadInterface = new MsgLoadInterface() {
InterfaceText = interfaceText
};

foreach (var connection in _connections.Values) {
connection.Session?.Channel.SendMessage(msgLoadInterface);
}
}
}
}

public sealed class HotReloadInterfaceCommand : IConsoleCommand {
// ReSharper disable once StringLiteralTypo
public string Command => "hotreloadinterface";
public string Description => "Reload the .dmf interface and send the update to all clients";
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 != 0) {
shell.WriteError("This command does not take any arguments!");
return;
}

DreamManager dreamManager = IoCManager.Resolve<DreamManager>();
dreamManager.HotReloadInterface();
shell.WriteLine("Reloading interface");
}
}
1 change: 1 addition & 0 deletions OpenDreamRuntime/Procs/Native/DreamProcNative.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ internal static class DreamProcNative {
objectTree.SetNativeProc(objectTree.World, DreamProcNativeWorld.NativeProc_GetConfig);
objectTree.SetNativeProc(objectTree.World, DreamProcNativeWorld.NativeProc_Profile);
objectTree.SetNativeProc(objectTree.World, DreamProcNativeWorld.NativeProc_SetConfig);
objectTree.SetNativeProc(objectTree.World, DreamProcNativeWorld.NativeProc_ODHotReloadInterface);

SetOverridableNativeProc(objectTree, objectTree.World, DreamProcNativeWorld.NativeProc_Reboot);
}
Expand Down
7 changes: 7 additions & 0 deletions OpenDreamRuntime/Procs/Native/DreamProcNativeWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,13 @@ internal static class DreamProcNativeWorld {
return DreamValue.Null;
}

[DreamProc("ODHotReloadInterface")]
public static DreamValue NativeProc_ODHotReloadInterface(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) {
var dreamManager = IoCManager.Resolve<DreamManager>();
dreamManager.HotReloadInterface();
return DreamValue.Null;
}

/// <summary>
/// Determines the specified configuration space and configuration set in a config_set argument
/// </summary>
Expand Down
21 changes: 15 additions & 6 deletions OpenDreamRuntime/Resources/DreamResourceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,11 @@ public sealed class DreamResourceManager {
return File.Exists(resourcePath);
}

public DreamResource LoadResource(string resourcePath) {
public DreamResource LoadResource(string resourcePath, bool forceReload = false) {
DreamResource resource;
int resourceId;

if (_resourcePathToId.TryGetValue(resourcePath, out int resourceId)) {
resource = _resourceCache[resourceId];
} else {
resourceId = _resourceCache.Count;

DreamResource GetResource() {
// Create a new type of resource based on its extension
switch (Path.GetExtension(resourcePath)) {
case ".dmi":
Expand All @@ -79,8 +76,20 @@ public sealed class DreamResourceManager {
break;
}

return resource;
}

if (!forceReload && _resourcePathToId.TryGetValue(resourcePath, out resourceId)) {
resource = _resourceCache[resourceId];
} else if(!forceReload) {
resourceId = _resourceCache.Count;
resource = GetResource();
_resourceCache.Add(resource);
_resourcePathToId.Add(resourcePath, resourceId);
} else {
resourceId = _resourcePathToId[resourcePath];
resource = GetResource();
_resourceCache[resourceId] = resource;
}

return resource;
Expand Down
10 changes: 8 additions & 2 deletions TestGame/TestInterface.dmf
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ menu "menu"
command = ".winset \"wingetwindow.is-visible=false?wingetwindow.is-visible=true:wingetwindow.is-visible=false\""
category = "Menu"


window "mapwindow"
elem "mapwindow"
type = MAIN
Expand Down Expand Up @@ -98,7 +99,8 @@ window "outputwindow"
size = 0x0
anchor1 = 0,0
anchor2 = 100,100
is-default = true
is-default = true

elem "input"
type = INPUT
pos = 0,460
Expand Down Expand Up @@ -146,6 +148,9 @@ window "testwindow"
size = 200x100
title = "popup"
is-visible = false
elem "testwindowlabel"
type = LABEL
text = "I am a test"
font-size = 6pt
elem "testwindowlabel"
type = LABEL
Expand Down Expand Up @@ -203,4 +208,5 @@ window "wingetwindow"
pos = 0,160
size = 0,20
text = "as raw"
command = "wingettextverb \"raw: [[wingetinput.text as raw]]\""
command = "wingettextverb \"raw: [[wingetinput.text as raw]]\""

6 changes: 6 additions & 0 deletions TestGame/code.dm
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@
set name = "wingettextverb"
world << "recieved: [rawtext]"

verb/test_hot_reload()
set category = "Test"
src << "trying hot reload of interface..."
world.ODHotReloadInterface()
src << "done hot reload of interface!"

/mob/Stat()
if (statpanel("Status"))
stat("tick_usage", world.tick_usage)
Expand Down

0 comments on commit 89f5e6c

Please sign in to comment.