Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable hot reloading of interface files #1779

Merged
merged 10 commits into from
Jun 9, 2024
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 @@ protected override void UpdateElementDescriptor() {
}
}

/// <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
13 changes: 12 additions & 1 deletion OpenDreamClient/Interface/DreamInterfaceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ private void RxLoadInterface(MsgLoadInterface message) {

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

private void RxUpdateClientInfo(MsgUpdateClientInfo msg) {
Expand Down Expand Up @@ -792,11 +794,20 @@ public void WinClone(string controlId, string cloneId) {

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
16 changes: 15 additions & 1 deletion OpenDreamRuntime/DreamManager.Connections.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ private void RxTopic(MsgTopic message) {
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 +279,18 @@ private void UpdateStat() {
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);
}
}
}

}
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 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) {
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
8 changes: 8 additions & 0 deletions OpenDreamRuntime/Procs/Native/DreamProcNativeWorld.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@
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
20 changes: 14 additions & 6 deletions OpenDreamRuntime/Resources/DreamResourceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,11 @@
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 @@ -78,9 +75,20 @@
resource = new DreamResource(resourceId, resourcePath, resourcePath);
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
Loading