Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
818 changes: 803 additions & 15 deletions Assets/Scenes/Compose.unity

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
using System;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using ArcCreate.Utility.Lua;
using Cysharp.Threading.Tasks;
using Google.MaterialDesign.Icons;
using MoonSharp.VsCodeDebugger;
using Newtonsoft.Json.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

namespace ArcCreate.Compose.EventsEditor
{
public class ScenecontrolDebugService : MonoBehaviour, IScriptDebugSetup
{
[SerializeField] private Toggle autoRebuildToggle;

[SerializeField] private Button debuggerButton;
[SerializeField] private MaterialIcon debuggerIcon;
[SerializeField] private TMP_Text debuggerText;

[SerializeField] private int debuggerClientDetectInterval = 500;
[SerializeField] private int debuggerClientDetectCount = 60;

[SerializeField] private int debuggerServerListenPort = 42020;

private JObject defaultVsCodeLaunchSettingEntry = null!;

private MoonSharpVsCodeDebugServer debugServer;
private DebuggerIndicatorState currentState = DebuggerIndicatorState.Disconnected;

public MoonSharpVsCodeDebugServer InitDebugServer()
{
if (currentState == DebuggerIndicatorState.Disabled) return null;

debugServer ??= new MoonSharpVsCodeDebugServer(debuggerServerListenPort).Start();

UpdateDebuggerIndicatorState(DebuggerIndicatorState.Disconnected);
return debugServer;
}

private const BindingFlags BindFlags = BindingFlags.Instance | BindingFlags.NonPublic;

private static bool HasActiveDebuggerClient(MoonSharpVsCodeDebugServer _debugServer)
{
// MoonSharp doesn't expose these required fields
// so use Reflection to get the information

var fCurrent = _debugServer.GetType().GetField("m_Current", BindFlags)!;
var currentDebuggerInstance = fCurrent.GetValue(_debugServer);
var fClient = currentDebuggerInstance.GetType().GetField("m_Client__", BindFlags)!;

return fClient.GetValue(currentDebuggerInstance) != null;
}

private enum DebuggerIndicatorState
{
Preparing,
Connected,
Disconnected,
Disabled
}

private void UpdateDebuggerIndicatorState(DebuggerIndicatorState state)
{
if (currentState == DebuggerIndicatorState.Disabled) return;

Color targetColor;

switch (state)
{
case DebuggerIndicatorState.Preparing:
{
targetColor = Color.yellow;

debuggerButton.interactable = false;
break;
}
case DebuggerIndicatorState.Connected:
{
targetColor = Color.green;

debuggerButton.interactable = false;
autoRebuildToggle.isOn = false;
autoRebuildToggle.interactable = false;
break;
}
case DebuggerIndicatorState.Disconnected:
{
targetColor = Color.white;

debuggerButton.interactable = true;
autoRebuildToggle.interactable = true;
break;
}
case DebuggerIndicatorState.Disabled:
{
targetColor = new Color(0.8980392f, 0.2235294f, 0.2235294f);

debuggerButton.interactable = false;
autoRebuildToggle.interactable = true;
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(state), state, null);
}

debuggerText.color = targetColor;
debuggerIcon.color = targetColor;

currentState = state;
}

public async UniTask<bool> AwaitDebuggerAttach()
{
UpdateDebuggerIndicatorState(DebuggerIndicatorState.Preparing);

// timeout: (debuggerClientDetectInterval) ms * debuggerClientDetectCount
for (int i = 0; i < debuggerClientDetectCount; i++)
{
if (HasActiveDebuggerClient(debugServer))
{
UpdateDebuggerIndicatorState(DebuggerIndicatorState.Connected);
return true;
}

await UniTask.Delay(debuggerClientDetectInterval, DelayType.Realtime);
}

UpdateDebuggerIndicatorState(DebuggerIndicatorState.Disconnected);
return false;
}

public void CleanDebugServer()
{
if (debugServer?.Current != null)
{
debugServer.Detach(debugServer.Current);
}

UpdateDebuggerIndicatorState(DebuggerIndicatorState.Disconnected);
}

public void GenerateVsCodeLaunchSettings(string filepath)
{
filepath = Path.Combine(filepath, ".vscode");
if (!Directory.Exists(filepath))
{
Directory.CreateDirectory(filepath);
}

filepath = Path.Combine(filepath, "launch.json");


try
{
if (File.Exists(filepath))
{
var json = JObject.Parse(File.ReadAllText(filepath));

if (!json.ContainsKey("configurations"))
{
json["configurations"] = new JArray();
}

var configurations = json["configurations"] as JArray;

bool hasMoonSharpConfig = configurations != null &&
configurations.Any(config =>
config["type"]?.Value<string>() == "moonsharp-debug");

if (!hasMoonSharpConfig)
{
configurations?.Add(defaultVsCodeLaunchSettingEntry);

File.WriteAllText(filepath, json.ToString());
}
}
else
{
File.WriteAllText(filepath, new JObject
{
["version"] = "0.2.0",
["configurations"] = new JArray
{
defaultVsCodeLaunchSettingEntry
}
}.ToString());
}
}
catch (Exception ex)
{
Debug.LogError($"Failed to parse or update launch.json: {ex.Message}");
}
}

private void Awake()
{
defaultVsCodeLaunchSettingEntry = new JObject
{
["name"] = "ArcCreate MoonSharp Attach",
["type"] = "moonsharp-debug",
["request"] = "attach",
["debugServer"] = debuggerServerListenPort
};
try
{
InitDebugServer();
}
catch (SocketException ex)
{
// catch SocketException, and disable debugger
Debug.LogWarning(
"Debugger has been initialized on another ArcCreate instance, self-disabled for this");

UpdateDebuggerIndicatorState(DebuggerIndicatorState.Disabled);
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
using System.Linq;
using System.Reflection;
using ArcCreate.Compose.Navigation;
using ArcCreate.Compose.Popups;
using ArcCreate.Gameplay.Data;
using ArcCreate.Gameplay.Scenecontrol;
using ArcCreate.Utility.Lua;
using Cysharp.Threading.Tasks;
using MoonSharp.Interpreter;
using UnityEngine;

Expand Down Expand Up @@ -75,12 +77,11 @@ public void SetupScript(Script script)
});
}

public void Rebuild()
public void Rebuild(bool isDebug = false)
{
Services.Gameplay.Scenecontrol.ScenecontrolFolder = Values.ScenecontrolFolder;
Clean();
RunScript();
ExecuteEvents();
RunScript(isDebug).ContinueWith(ExecuteEvents).Forget();
}

public void GenerateEmmyLua()
Expand Down Expand Up @@ -166,31 +167,96 @@ private void ShowError(string e)
Debug.LogError(e);
}

private void RunScript()
private async UniTask RunScript(bool isDebug = false)
{
Script script = new Script();
string folderPath = Values.ScenecontrolFolder;

UserData.RegisterAssembly();
AddBuiltInTypes();

const string initFileName = "init.lua";

string currentChartName = Services.Project.CurrentChart.ChartPath;
string initPath = Path.Combine(folderPath, "init.lua");
string perChartPath = Path.Combine(folderPath, Path.GetFileNameWithoutExtension(currentChartName) + ".lua");
string perChartFileName = Path.GetFileNameWithoutExtension(currentChartName) + ".lua";

string initPath = Path.Combine(folderPath, initFileName);
string perChartPath = Path.Combine(folderPath, perChartFileName);
string lastPath = initPath;

try
{
var debugServer = isDebug ? Services.ScenecontrolDebug.InitDebugServer() : null;

Script initScript = null;
Script perChartScript = null;

if (File.Exists(initPath))
{
lastPath = initPath;
LuaRunner.RunScript(File.ReadAllText(initPath), this, new ScriptLoader(folderPath));
initScript = LuaRunner.RunScript(await File.ReadAllTextAsync(initPath),
this,
new ScriptLoader(folderPath),
initFileName,
folderPath,
debugServer);
}

if (File.Exists(perChartPath))
{
lastPath = perChartPath;
LuaRunner.RunScript(File.ReadAllText(perChartPath), this, new ScriptLoader(folderPath));
perChartScript = LuaRunner.RunScript(await File.ReadAllTextAsync(perChartPath),
this,
new ScriptLoader(folderPath),
perChartFileName,
folderPath,
debugServer);
}

if (isDebug && debugServer != null)
{
Debug.Log("Waiting for VsCode debugger to attach");
bool isAttached = await Services.ScenecontrolDebug.AwaitDebuggerAttach();
if (!isAttached)
{
Debug.LogWarning("VsCode debugger timeout, continue to run the script");
}
else
{
Debug.Log("VsCode debugger attached");

// update the canvas before hitting breakpoint
Canvas.ForceUpdateCanvases();
await UniTask.Delay(500); // a window for changes to take place

const string debugEntrypoint = "DEBUG_ENTRYPOINT";

if (initScript != null)
{
if (initScript.Globals[debugEntrypoint] != null)
{
initScript.Call(initScript.Globals[debugEntrypoint]);
}
else
{
Debug.Log($"Unable to find debug entrypoint for '{initFileName}'");
}
}

if (perChartScript != null)
{
if (perChartScript.Globals[debugEntrypoint] != null)
{
perChartScript.Call(perChartScript.Globals[debugEntrypoint]);
}
else
{
Debug.Log($"Unable to find debug entrypoint for '{perChartFileName}'");
}
}
}

Services.ScenecontrolDebug.CleanDebugServer();
Debug.Log("VsCode debugger detached");
}
}
catch (Exception e)
Expand All @@ -212,6 +278,7 @@ private void Clean()
scenecontrolTypes.Clear();
scTable.ClearTypes();
Services.Gameplay.Scenecontrol.Clean();
Services.ScenecontrolDebug.CleanDebugServer();
}

private void AddBuiltInTypes()
Expand Down
Loading