mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-11-04 12:51:48 +00:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7fb9af0c7 | ||
|
|
fff1abf012 | ||
|
|
8e9efbb367 | ||
|
|
8244033ac5 | ||
|
|
e3619ae93c | ||
|
|
0087bca27b | ||
|
|
9119613e62 | ||
|
|
1636f3a5d0 | ||
|
|
886d2760bc | ||
|
|
6da69aad03 | ||
|
|
6c184aafe0 | ||
|
|
5c18f61da4 | ||
|
|
1435dac4d6 | ||
|
|
abf7a01917 | ||
|
|
c8d63a9c36 | ||
|
|
9ccaf8977a | ||
|
|
e758d6fd09 | ||
|
|
1cc604cd51 |
@@ -21,7 +21,7 @@ IconFile = "$(ProjectDir)/Resources/Textures/GlitchyEngineIcon.ico"
|
||||
|
||||
[Configs.Debug.Win64]
|
||||
PostBuildCmds = ["CopyFilesIfNewer(\"$(WorkspaceDir)/bin/vswhere.exe\", \"$(TargetDir)\")"]
|
||||
DebugCommandArguments = "\"D:\\Development\\Git\\SingleStateToOrbit\""
|
||||
DebugCommandArguments = "\"$(ProjectDir)/../GlitchyEngine.Test/content/TestProject\""
|
||||
PreprocessorMacros = ["DEBUG", "IMGUI"]
|
||||
|
||||
[Configs.Release.Win64]
|
||||
|
||||
@@ -13,9 +13,6 @@ namespace GlitchyEditor.CodeEditors;
|
||||
/// Runs on its own worker thread so the editor never blocks on a busy or starting Visual Studio.
|
||||
class DteOpenRequest
|
||||
{
|
||||
private const int ColdStartMaxPolls = 120;
|
||||
private const int ColdStartPollIntervalMs = 500;
|
||||
|
||||
private append String _solutionPath = .();
|
||||
private append String _devenvPath = .();
|
||||
private append String _fileName = .();
|
||||
@@ -40,6 +37,7 @@ class DteOpenRequest
|
||||
}
|
||||
|
||||
public int LineNumber;
|
||||
public int ColumnNumber;
|
||||
|
||||
private static int32 sWorkerActive = 0;
|
||||
|
||||
@@ -83,7 +81,7 @@ class DteOpenRequest
|
||||
if (FileName.IsEmpty)
|
||||
VisualStudioDte.ActivateMainWindow(dte);
|
||||
else
|
||||
VisualStudioDte.OpenFileAtLine(dte, FileName, LineNumber).IgnoreError();
|
||||
VisualStudioDte.OpenFileAtLine(dte, FileName, LineNumber, ColumnNumber).IgnoreError();
|
||||
|
||||
delete dte;
|
||||
|
||||
@@ -107,13 +105,13 @@ class DteOpenRequest
|
||||
|
||||
// Wait for the new instance to finish loading the solution (Solution.FullName stays empty
|
||||
// until then), then open the file in it.
|
||||
for (int i < ColdStartMaxPolls)
|
||||
for (int i < VisualStudioDte.MaxPolls)
|
||||
{
|
||||
Thread.Sleep(ColdStartPollIntervalMs);
|
||||
Thread.Sleep(VisualStudioDte.PollIntervalMs);
|
||||
|
||||
if (VisualStudioDte.FindRunningInstance(SolutionPath) case .Ok(let startedDte))
|
||||
{
|
||||
VisualStudioDte.OpenFileAtLine(startedDte, FileName, LineNumber).IgnoreError();
|
||||
VisualStudioDte.OpenFileAtLine(startedDte, FileName, LineNumber, ColumnNumber).IgnoreError();
|
||||
|
||||
delete startedDte;
|
||||
return;
|
||||
|
||||
@@ -14,6 +14,9 @@ namespace GlitchyEditor.CodeEditors;
|
||||
/// All methods must be called from a thread that is initialized for COM (CoInitializeEx).
|
||||
static class VisualStudioDte
|
||||
{
|
||||
public const int MaxPolls = 120;
|
||||
public const int PollIntervalMs = 500;
|
||||
|
||||
private const String MonikerPrefix = "!VisualStudio.DTE";
|
||||
/// EnvDTE.Constants.vsViewKindCode
|
||||
private const String ViewKindCode = "{7651A701-06E5-11D1-8EBD-00A0C90F26EA}";
|
||||
@@ -107,7 +110,7 @@ static class VisualStudioDte
|
||||
|
||||
/// Opens the given file in the code editor of the given Visual Studio instance and
|
||||
/// jumps to lineNumber (1-based, ignored if <= 0).
|
||||
public static Result<void> OpenFileAtLine(ComDispatch dte, StringView fileName, int lineNumber)
|
||||
public static Result<void> OpenFileAtLine(ComDispatch dte, StringView fileName, int lineNumber, int columnNumber)
|
||||
{
|
||||
ComDispatch itemOperations = Try!(dte.GetObjectProperty("ItemOperations"));
|
||||
defer delete itemOperations;
|
||||
@@ -125,7 +128,7 @@ static class VisualStudioDte
|
||||
}
|
||||
|
||||
if (lineNumber > 0)
|
||||
GoToLine(dte, lineNumber);
|
||||
GoToLine(dte, lineNumber, columnNumber);
|
||||
|
||||
ActivateMainWindow(dte);
|
||||
|
||||
@@ -198,18 +201,18 @@ static class VisualStudioDte
|
||||
return path;
|
||||
}
|
||||
|
||||
private static void GoToLine(ComDispatch dte, int lineNumber)
|
||||
private static void GoToLine(ComDispatch dte, int lineNumber, int columnNumber)
|
||||
{
|
||||
ComDispatch document = null;
|
||||
defer { delete document; }
|
||||
|
||||
// Directly after OpenFile the ActiveDocument might not be set yet, so retry for a bit.
|
||||
for (int i < 10)
|
||||
for (int i < MaxPolls)
|
||||
{
|
||||
if (dte.GetObjectProperty("ActiveDocument") case .Ok(out document))
|
||||
break;
|
||||
|
||||
Thread.Sleep(100);
|
||||
Thread.Sleep(PollIntervalMs);
|
||||
}
|
||||
|
||||
if (document == null)
|
||||
@@ -224,9 +227,14 @@ static class VisualStudioDte
|
||||
return;
|
||||
}
|
||||
|
||||
VARIANT[2] gotoLineArgs = .(VARIANT.FromInt32((int32)lineNumber), VARIANT.FromBool(false));
|
||||
/*VARIANT[2] gotoLineArgs = .(VARIANT.FromInt32((int32)lineNumber), VARIANT.FromBool(false));
|
||||
|
||||
if (selection.InvokeMethod("GotoLine", gotoLineArgs) case .Ok(var gotoResult))
|
||||
VariantClear(&gotoResult);*/
|
||||
|
||||
VARIANT[3] gotoLineArgs = .(VARIANT.FromInt32((int32)lineNumber), VARIANT.FromInt32((int32)columnNumber), VARIANT.FromBool(false));
|
||||
|
||||
if (selection.InvokeMethod("MoveTo", gotoLineArgs) case .Ok(var gotoResult))
|
||||
VariantClear(&gotoResult);
|
||||
|
||||
delete selection;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace GlitchyEditor.CodeEditors;
|
||||
|
||||
interface IIdeAdapter
|
||||
{
|
||||
void OpenScript(StringView fileName, int lineNumber = 0);
|
||||
/// Opens the current script solution and the specified script file in the IDE. Allows to specify a line number and a column that the cursor will be moved to.
|
||||
/// @param fileName The absolute path to the script file. Can be null to just open the script project (Note: the same thing can be achieved by using @see OpenScriptProject).
|
||||
/// @param lineNumber The line number that the cursor will be placed at (1-based, so 1 is first line).
|
||||
/// @param lineNumber The column number that the cursor will be placed at (1-based, so 1 is the start of the line).
|
||||
void OpenScript(StringView fileName, int lineNumber = 0, int columnNumber = 0);
|
||||
|
||||
/// Opens the current script solution in the IDE.
|
||||
void OpenScriptProject();
|
||||
}
|
||||
|
||||
@@ -21,14 +21,15 @@ class RiderIdeAdapter : IIdeAdapter
|
||||
_ideInstallation = ideInstallation;
|
||||
}
|
||||
|
||||
public void OpenScript(StringView fileName, int lineNumber)
|
||||
public void OpenScript(StringView fileName, int lineNumber, int columnNumber)
|
||||
{
|
||||
String solutionPath = scope .();
|
||||
Editor.Instance.CurrentProject.GetPathToScriptSolutionFile(solutionPath);
|
||||
|
||||
ProcessStartInfo startInfo = scope .();
|
||||
startInfo.SetFileName(_ideInstallation.Path);
|
||||
startInfo.SetArguments(scope $"{solutionPath} --line {lineNumber} {fileName}");
|
||||
// For rider columns appear to be 0-indexed
|
||||
startInfo.SetArguments(scope $"\"{solutionPath}\" --line {lineNumber} --column {columnNumber - 1} \"{fileName}\"");
|
||||
|
||||
scope SpawnedProcess().Start(startInfo);
|
||||
}
|
||||
|
||||
@@ -18,17 +18,17 @@ class VisualStudioIdeAdapter : IIdeAdapter
|
||||
_ideInstallation = ideInstallation;
|
||||
}
|
||||
|
||||
public void OpenScript(StringView fileName, int lineNumber)
|
||||
public void OpenScript(StringView fileName, int lineNumber, int columnNumber)
|
||||
{
|
||||
Open(fileName, lineNumber);
|
||||
Open(fileName, lineNumber, columnNumber);
|
||||
}
|
||||
|
||||
public void OpenScriptProject()
|
||||
{
|
||||
Open(null, 0);
|
||||
Open(null, 0, 0);
|
||||
}
|
||||
|
||||
private void Open(StringView fileName, int lineNumber)
|
||||
private void Open(StringView fileName, int lineNumber, int columnNumber)
|
||||
{
|
||||
String solutionPath = scope String();
|
||||
Editor.Instance.CurrentProject.GetPathToScriptSolutionFile(solutionPath);
|
||||
@@ -57,6 +57,7 @@ class VisualStudioIdeAdapter : IIdeAdapter
|
||||
request.DevenvPath = _ideInstallation.Path;
|
||||
request.FileName = fileName;
|
||||
request.LineNumber = lineNumber;
|
||||
request.ColumnNumber = columnNumber;
|
||||
|
||||
DteOpenRequest.Start(request);
|
||||
#else
|
||||
|
||||
@@ -66,6 +66,8 @@ namespace GlitchyEditor.EditWindows
|
||||
{
|
||||
_entityToHighlight = e;
|
||||
|
||||
SelectEntity(e, true);
|
||||
|
||||
Entity walker = _entityToHighlight;
|
||||
|
||||
while (walker.Parent != null)
|
||||
|
||||
@@ -8,6 +8,7 @@ using GlitchyEngine.Scripting;
|
||||
using GlitchyEngine.Renderer;
|
||||
using GlitchyEngine;
|
||||
using GlitchyEditor.CodeEditors;
|
||||
using System.IO;
|
||||
|
||||
namespace GlitchyEditor.EditWindows;
|
||||
|
||||
@@ -295,12 +296,30 @@ class LogWindow : EditorWindow
|
||||
{
|
||||
if (ImGui.CollapsingHeader(message.Message.Ptr))
|
||||
{
|
||||
// Show the native to managed entry point only if we show engine messages
|
||||
String lineBuffer = scope .();
|
||||
for (StackFrameInfo frameInfo in message.Source.Exception.StackTrace.Frames)
|
||||
{
|
||||
String relativeSourcePath = scope .(frameInfo.FileName);
|
||||
|
||||
if (_showEngineMessages)
|
||||
ImGui.TextUnformatted(message.Source.Exception.StackTrace);
|
||||
else
|
||||
ImGui.TextUnformatted(message.Source.Exception.CleanStackTrace);
|
||||
relativeSourcePath.Replace(Editor.Instance.ContentManager.ResourcesDirectory, "Resources");
|
||||
relativeSourcePath.Replace(Editor.Instance.ContentManager.AssetDirectory, "Assets");
|
||||
|
||||
//Path.GetRelativePath(frameInfo.FileName, Editor.Instance.CurrentProject.WorkspacePath, relativeSourcePath);
|
||||
|
||||
StackFrameInfo sfi = scope .(relativeSourcePath, frameInfo.MethodSignature, frameInfo.Line, frameInfo.Column);
|
||||
|
||||
//_editor.CurrentProject.PathInProject();
|
||||
|
||||
lineBuffer.Clear();
|
||||
lineBuffer.SetF($" {sfi}");
|
||||
|
||||
float width = ImGui.CalcTextSize(lineBuffer.CStr()).x;
|
||||
if (ImGui.Selectable(lineBuffer, false, .None, .(width, 0)))
|
||||
{
|
||||
// TODO: Columns?
|
||||
Editor.Instance.IdeAdapter.OpenScript(frameInfo.FileName, frameInfo.Line, frameInfo.Column);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.NewLine();
|
||||
}
|
||||
@@ -319,6 +338,13 @@ class LogWindow : EditorWindow
|
||||
{
|
||||
Editor.Instance.IdeAdapter.OpenScript(message.Source.MessageOrigin.FileName, message.Source.MessageOrigin.LineNumber);
|
||||
}
|
||||
else if (message.Source.Exception != null)
|
||||
{
|
||||
// TODO: Column?
|
||||
let firstFrame = message.Source.Exception.StackTrace.Frames[0];
|
||||
Editor.Instance.IdeAdapter.OpenScript(firstFrame.FileName, firstFrame.Line);
|
||||
//message.Source.Exception.StackTrace
|
||||
}
|
||||
}
|
||||
|
||||
// Dont show the counter if we only have one message.
|
||||
@@ -373,15 +399,8 @@ class LogWindow : EditorWindow
|
||||
|
||||
public void LogException(DateTime timestamp, ScriptException exception)
|
||||
{
|
||||
StringView firstLine = exception.StackTrace;
|
||||
|
||||
int firstInIndex = exception.StackTrace.IndexOf("\n");
|
||||
|
||||
if (firstInIndex != -1)
|
||||
firstLine = firstLine.Substring(0, firstInIndex);
|
||||
|
||||
String message = scope .(128);
|
||||
message.AppendF($"Exception: \"{exception.FullName}\" | Message: \"{exception.Message}\" {firstLine}\0");
|
||||
message.AppendF($"Exception: \"{exception.FullName}\" | Message: \"{exception.Message}\" {exception.StackTrace.Frames[0]}\0");
|
||||
|
||||
// TODO: are mono exceptions never engine only?
|
||||
LogMessage logMessage = new LogMessage(timestamp, message, .Error, new MessageSource(){Entity = exception.EntityId, Exception = exception, IsEngineMessage = false});
|
||||
|
||||
@@ -75,14 +75,6 @@ namespace GlitchyEditor
|
||||
|
||||
private float _fixedTimestep = 1.0f / 60.0f;
|
||||
|
||||
append List<(String Name, String Path)> _recentScenePaths = .() ~ {
|
||||
for (var item in _)
|
||||
{
|
||||
delete item.Name;
|
||||
delete item.Path;
|
||||
}
|
||||
};
|
||||
|
||||
private GameTime _simulationGameTime = new .() ~ delete _;
|
||||
|
||||
/// Gets or sets the path of the current scene.
|
||||
@@ -720,6 +712,8 @@ namespace GlitchyEditor
|
||||
|
||||
private void CloseCurrentProject()
|
||||
{
|
||||
Log.EngineLogger.Trace("Closing current Project...");
|
||||
|
||||
CloseCurrentScene();
|
||||
|
||||
SetEditorScene(null);
|
||||
@@ -729,6 +723,8 @@ namespace GlitchyEditor
|
||||
// TODO: Do actual work here!
|
||||
delete _currentProject;
|
||||
_currentProject = null;
|
||||
|
||||
Log.EngineLogger.Trace("Project closed.");
|
||||
}
|
||||
|
||||
private Result<void> LoadAndOpenProject(StringView workspacePath)
|
||||
@@ -749,32 +745,46 @@ namespace GlitchyEditor
|
||||
{
|
||||
if (project == null)
|
||||
return .Err;
|
||||
|
||||
|
||||
CloseCurrentProject();
|
||||
|
||||
Log.EngineLogger.Trace("Opening Project...");
|
||||
|
||||
_currentProject = project;
|
||||
|
||||
_editor.CurrentProject = _currentProject;
|
||||
|
||||
Log.EngineLogger.Trace("Preparing asset manager...");
|
||||
|
||||
String appAssemblyPath = scope String();
|
||||
_contentManager.SetAssetCacheDirectory(_currentProject.GetScopedPath!(".cache"));
|
||||
_contentManager.SetAssetDirectory(_currentProject.AssetsFolder);
|
||||
|
||||
_currentProject.PathInProject(appAssemblyPath, scope $".cache/bin/{_currentProject.Name}.dll");
|
||||
|
||||
ScriptEngine.SetAppAssemblyPath(appAssemblyPath);
|
||||
|
||||
Log.EngineLogger.Trace("Loading script assembly...");
|
||||
ScriptEngine.SetAppAssemblyPath(appAssemblyPath, retainScriptData: false);
|
||||
|
||||
if (!_currentProject.UserSettings.LastOpenedScene.IsWhiteSpace)
|
||||
{
|
||||
String lastSceneFile = _currentProject.GetScopedPath!(_currentProject.UserSettings.LastOpenedScene);
|
||||
|
||||
if (File.Exists(lastSceneFile))
|
||||
{
|
||||
Log.EngineLogger.Trace("Opening last opened scene...");
|
||||
LoadSceneFile(lastSceneFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.EngineLogger.Trace("Opening new scene...");
|
||||
CreateAndOpenNewScene();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.EngineLogger.Trace("Opening new scene...");
|
||||
CreateAndOpenNewScene();
|
||||
}
|
||||
|
||||
UpdateWindowTitle();
|
||||
|
||||
@@ -784,6 +794,8 @@ namespace GlitchyEditor
|
||||
EditorApp.Instance.Settings.Save();
|
||||
}
|
||||
|
||||
Log.EngineLogger.Trace("Project opened.");
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
@@ -808,27 +820,34 @@ namespace GlitchyEditor
|
||||
append ScriptInstanceSerializer _scriptSerializer = .();
|
||||
|
||||
/// Starts the play mode for the current scene
|
||||
private void OnScenePlay()
|
||||
private void OnScenePlay(SceneState newSceneState)
|
||||
{
|
||||
Debug.Profiler.ProfileFunction!();
|
||||
|
||||
Runtime.Assert(newSceneState == .Play || newSceneState == .Simulate, "newSceneState must either be Play or Simulate");
|
||||
|
||||
Log.EngineLogger.AssertDebug(_scriptSerializer.SerializedObjectCount == 0, "Somehow some entities are serialized.");
|
||||
|
||||
if (EditorApp.Instance.Settings.EditorSettings.ClearLogOnPlay)
|
||||
Editor.Instance.LogWindow.ClearLog();
|
||||
|
||||
/// Save the in memory state of the script intances so se can recreate it after stopping play/simulation
|
||||
_scriptSerializer.SerializeScriptInstances();
|
||||
|
||||
_editor.SceneViewportWindow.EditorMode = false;
|
||||
_sceneState = .Play;
|
||||
_sceneState = newSceneState;
|
||||
|
||||
using (Scene runtimeScene = new Scene())
|
||||
{
|
||||
_editorScene.CopyTo(runtimeScene, true);
|
||||
// In simulation mode we don't want any scripts to run, just physics.
|
||||
_editorScene.CopyTo(runtimeScene, _sceneState == .Play);
|
||||
|
||||
SetActiveScene(runtimeScene, startRuntime: true, startSimulation: true, newPlayMode: .Play);
|
||||
SetActiveScene(runtimeScene, startRuntime: (_sceneState == .Play), startSimulation: true, _sceneState);
|
||||
|
||||
_scriptSerializer.DeserializeScriptInstances();
|
||||
if (_sceneState == .Play)
|
||||
{
|
||||
_scriptSerializer.DeserializeScriptInstances();
|
||||
}
|
||||
}
|
||||
|
||||
_editor.CurrentScene = _activeScene;
|
||||
@@ -837,34 +856,25 @@ namespace GlitchyEditor
|
||||
SwitchToPlayWindow();
|
||||
}
|
||||
|
||||
enum PlayMode
|
||||
{
|
||||
Play,
|
||||
Editor,
|
||||
Simulation
|
||||
}
|
||||
|
||||
/// Activates the given scene.
|
||||
/// @param scene The scene to be activated.
|
||||
/// @param startRuntime If set to true, the script runtime will be initialized for the given scene.
|
||||
/// @param startSimulation If set to true, the physics simulation will be initialized for the given scene.
|
||||
private void SetActiveScene(Scene scene, bool startRuntime, bool startSimulation, PlayMode? newPlayMode = null)
|
||||
/// @param newSceneState The new scene state.
|
||||
private void SetActiveScene(Scene scene, bool startRuntime, bool startSimulation, SceneState newSceneState)
|
||||
{
|
||||
_activeScene?.Stop();
|
||||
|
||||
if (newPlayMode != null)
|
||||
switch (newSceneState)
|
||||
{
|
||||
switch (newPlayMode.Value)
|
||||
{
|
||||
case .Play:
|
||||
ScriptEngine.ApplicationInfo.IsInPlayMode = true;
|
||||
case .Editor:
|
||||
ScriptEngine.ApplicationInfo.IsInEditMode = true;
|
||||
case .Simulation:
|
||||
// No scripts in Simulation
|
||||
ScriptEngine.ApplicationInfo.IsInEditMode = false;
|
||||
ScriptEngine.ApplicationInfo.IsInPlayMode = false;
|
||||
}
|
||||
case .Play:
|
||||
ScriptEngine.ApplicationInfo.IsInPlayMode = true;
|
||||
case .Edit:
|
||||
ScriptEngine.ApplicationInfo.IsInEditMode = true;
|
||||
case .Simulate:
|
||||
// No scripts in Simulation
|
||||
ScriptEngine.ApplicationInfo.IsInEditMode = false;
|
||||
ScriptEngine.ApplicationInfo.IsInPlayMode = false;
|
||||
}
|
||||
|
||||
if (scene != null)
|
||||
@@ -875,25 +885,6 @@ namespace GlitchyEditor
|
||||
SetReference!(_activeScene, scene);
|
||||
}
|
||||
|
||||
/// Starts the physics simulation mode for the current scene
|
||||
private void OnSceneSimulate()
|
||||
{
|
||||
_editor.SceneViewportWindow.EditorMode = false;
|
||||
_sceneState = .Simulate;
|
||||
|
||||
using (Scene simulationScene = new Scene())
|
||||
{
|
||||
_editorScene.CopyTo(simulationScene, false);
|
||||
|
||||
SetActiveScene(simulationScene, startRuntime: false, startSimulation: true, newPlayMode: .Simulation);
|
||||
}
|
||||
|
||||
_editor.CurrentScene = _activeScene;
|
||||
|
||||
if (EditorApp.Instance.Settings.EditorSettings.SwitchToPlayerOnSimulate)
|
||||
SwitchToPlayWindow();
|
||||
}
|
||||
|
||||
/// Pauses the scene
|
||||
private void OnScenePause()
|
||||
{
|
||||
@@ -923,14 +914,14 @@ namespace GlitchyEditor
|
||||
{
|
||||
if (_activeScene == null)
|
||||
return;
|
||||
|
||||
SetActiveScene(_editorScene, startRuntime: true, startSimulation: false, newPlayMode: .Editor);
|
||||
|
||||
// TODO: _editorScene.DeserializeScripts(scriptData);
|
||||
|
||||
|
||||
Log.EngineLogger.Trace("Stopping current scene and returing to edit mode...");
|
||||
|
||||
_editor.SceneViewportWindow.EditorMode = true;
|
||||
_sceneState = .Edit;
|
||||
|
||||
SetActiveScene(_editorScene, startRuntime: true, startSimulation: false, _sceneState);
|
||||
|
||||
_editor.CurrentScene = _activeScene;
|
||||
|
||||
_isPaused = false;
|
||||
@@ -943,14 +934,19 @@ namespace GlitchyEditor
|
||||
* into "Editor"-mode (because Game-Mode works on a copy of the scene).
|
||||
*/
|
||||
GameViewportSizeChanged(null, _editor.GameViewportWindow.RenderedViewportSize);
|
||||
|
||||
if (_scriptSerializer.SerializedObjectCount != 0)
|
||||
{
|
||||
// Reconstruct state before play
|
||||
_scriptSerializer.DeserializeScriptInstances();
|
||||
_scriptSerializer.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (EditorApp.Instance.Settings.EditorSettings.SwitchToEditorOnStop)
|
||||
SwitchToEditorWindow();
|
||||
|
||||
// Reconstruct state before play
|
||||
_scriptSerializer.DeserializeScriptInstances();
|
||||
_scriptSerializer.Clear();
|
||||
Log.EngineLogger.Trace("Switched to edit mode.");
|
||||
}
|
||||
|
||||
/// Stops the scene and cleans up the subsystems to allow loading another scene.
|
||||
@@ -983,6 +979,8 @@ namespace GlitchyEditor
|
||||
/// Sets the current editor scene
|
||||
private void SetEditorScene(Scene scene, ScriptInstanceSerializer serializedObjects = null)
|
||||
{
|
||||
Log.EngineLogger.Trace($"Switching to scene \"{(scene?.Name ?? "<none>")}\"...");
|
||||
|
||||
_editorScene?.Stop();
|
||||
|
||||
SetReference!(_editorScene, scene);
|
||||
@@ -1002,6 +1000,7 @@ namespace GlitchyEditor
|
||||
}
|
||||
|
||||
UpdateWindowTitle();
|
||||
Log.EngineLogger.Trace($"Switched scene to \"{(scene?.Name ?? "<none>")}\".");
|
||||
}
|
||||
|
||||
/// Creates a new default scene.
|
||||
@@ -1239,7 +1238,7 @@ namespace GlitchyEditor
|
||||
ImGui.PushID(0);
|
||||
|
||||
if (ImGui.ImageButton("", _editorIcons.Play, .(size, size), .Zero, .Ones))
|
||||
OnScenePlay();
|
||||
OnScenePlay(.Play);
|
||||
|
||||
ImGui.PopID();
|
||||
|
||||
@@ -1250,7 +1249,7 @@ namespace GlitchyEditor
|
||||
ImGui.PushID(1);
|
||||
|
||||
if (ImGui.ImageButton("", _editorIcons.Simulate, .(size, size), .Zero, .Ones))
|
||||
OnSceneSimulate();
|
||||
OnScenePlay(.Simulate);
|
||||
|
||||
ImGui.PopID();
|
||||
|
||||
@@ -1522,7 +1521,7 @@ namespace GlitchyEditor
|
||||
if(ImGui.BeginMenu("Tools", true))
|
||||
{
|
||||
if(ImGui.MenuItem("Reload Scripts"))
|
||||
ScriptEngine.ReloadAssemblies();
|
||||
ScriptEngine.ReloadAssemblies(retainScriptData: true);
|
||||
|
||||
#if GE_EDITOR_IMGUI_DEMO
|
||||
ImGui.Checkbox("Show ImGui Demo", &_showImguiDemoWindow);
|
||||
|
||||
@@ -139,7 +139,7 @@ public class EditorLogger : Logger
|
||||
}
|
||||
else if (args.Count > 0 && messageOrigin != null)
|
||||
{
|
||||
Editor.Instance.LogWindow.Log(timestamp, level, message, new .() {IsEngineMessage = IsEngineLogger, MessageOrigin = new MessageOrigin(messageOrigin.FileName, messageOrigin.LineNumber)});
|
||||
Editor.Instance.LogWindow.Log(timestamp, level, message, new .() {Entity = (messageOrigin.EntityId != .Zero ? messageOrigin.EntityId : null), IsEngineMessage = IsEngineLogger, MessageOrigin = new MessageOrigin(messageOrigin.EntityId, messageOrigin.FileName, messageOrigin.CallerMemberName, messageOrigin.LineNumber, messageOrigin.ColumnNumber)});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+148
-48
@@ -36,7 +36,10 @@ class Project
|
||||
[AllowAppend]
|
||||
private this(StringView workspacePath)
|
||||
{
|
||||
_workspacePath = new String(workspacePath);
|
||||
_workspacePath = new String(workspacePath.Length);
|
||||
Path.GetFullPath(workspacePath, _workspacePath);
|
||||
if (_workspacePath.EndsWith(Path.DirectorySeparatorChar))
|
||||
_workspacePath.RemoveFromEnd(1);
|
||||
|
||||
_assetsFolder = new String();
|
||||
PathInProject(_assetsFolder, "Assets");
|
||||
@@ -79,6 +82,27 @@ class Project
|
||||
Path.Combine(outTarget, "../ScriptCore/ScriptCore.csproj");
|
||||
Path.ToActualPath(outTarget);
|
||||
}
|
||||
|
||||
private static void GetScriptCoreGeneratorProjectFilePath(String outTarget)
|
||||
{
|
||||
Directory.GetCurrentDirectory(outTarget);
|
||||
Path.Combine(outTarget, "../ScriptCoreGenerator/ScriptCoreGenerator.csproj");
|
||||
Path.ToActualPath(outTarget);
|
||||
}
|
||||
|
||||
private static void GetScriptCoreGeneratorTestProjectFilePath(String outTarget)
|
||||
{
|
||||
Directory.GetCurrentDirectory(outTarget);
|
||||
Path.Combine(outTarget, "../ScriptCoreGenerator.Test/ScriptCoreGenerator.Test.csproj");
|
||||
Path.ToActualPath(outTarget);
|
||||
}
|
||||
|
||||
private static void GetImGuiDotNetProjectFilePath(String outTarget)
|
||||
{
|
||||
Directory.GetCurrentDirectory(outTarget);
|
||||
Path.Combine(outTarget, "../vendor/ImGui.NET/src/ImGui.NET/ImGui.NET.csproj");
|
||||
Path.ToActualPath(outTarget);
|
||||
}
|
||||
|
||||
/// Loads or creates the user specific settings for this project.
|
||||
private void InitUserSettings()
|
||||
@@ -274,7 +298,8 @@ class Project
|
||||
return project;
|
||||
}
|
||||
|
||||
/// If necessary adds or removes the reference to ScriptCore.csproj from the solution (.slnx) file.
|
||||
/// If missing adds the reference to ScriptCore and other script dependencies from the solution (.slnx) file.
|
||||
/// @param referenceScriptCoreProject If true, ScriptCore and other dependencies will be referenced using their .csproj-files. Otherwise the ScriptCore-dll will be referenced.
|
||||
private Result<void> FixupScriptSolutionFile(bool referenceScriptCoreProject)
|
||||
{
|
||||
String solutionPath = scope .();
|
||||
@@ -293,52 +318,94 @@ class Project
|
||||
|
||||
solution.LoadFromString(solutionFileContent);
|
||||
|
||||
XmlNode scriptCoreProjectReference = null;
|
||||
bool changedContent = false;
|
||||
|
||||
String scriptCoreFilePath = scope .();
|
||||
GetScriptCoreProjectFilePath(scriptCoreFilePath);
|
||||
changedContent |= Try!(AddProjectToSolution(solution, scriptCoreFilePath, referenceScriptCoreProject));
|
||||
|
||||
String scriptCoreGeneratorFilePath = scope .();
|
||||
GetScriptCoreGeneratorProjectFilePath(scriptCoreGeneratorFilePath);
|
||||
changedContent |= Try!(AddProjectToSolution(solution, scriptCoreGeneratorFilePath, referenceScriptCoreProject));
|
||||
|
||||
String scriptCoreGeneratorTestFilePath = scope .();
|
||||
GetScriptCoreGeneratorTestProjectFilePath(scriptCoreGeneratorTestFilePath);
|
||||
changedContent |= Try!(AddProjectToSolution(solution, scriptCoreGeneratorTestFilePath, referenceScriptCoreProject));
|
||||
|
||||
String imGuiDotNetFilePath = scope .();
|
||||
GetImGuiDotNetProjectFilePath(imGuiDotNetFilePath);
|
||||
changedContent |= Try!(AddProjectToSolution(solution, imGuiDotNetFilePath, referenceScriptCoreProject));
|
||||
|
||||
if (changedContent)
|
||||
{
|
||||
Log.EngineLogger.Info("Script solution file updated, saving...");
|
||||
|
||||
String modifiedSolution = new String(solutionFileContent.Length * 2);
|
||||
defer delete modifiedSolution;
|
||||
solution.SaveToString(modifiedSolution);
|
||||
|
||||
if (File.WriteAllText(solutionPath, modifiedSolution) case .Err)
|
||||
{
|
||||
Log.EngineLogger.Error($"Failed to modify {solutionPath}");
|
||||
return .Err;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.EngineLogger.Info("Script solution file already up to date.");
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
}
|
||||
|
||||
private Result<bool> AddProjectToSolution(Xml solution, StringView projectFilePath, bool referenceScriptCoreProject)
|
||||
{
|
||||
String projectName = scope .();
|
||||
Path.GetFileName(projectFilePath, projectName);
|
||||
|
||||
XmlNode projectReference = null;
|
||||
for (XmlNode projectNode in solution.DocumentElement.EnumerateNodes("Project", recursive: true))
|
||||
{
|
||||
if (projectNode.TryGetAttribute("Path", let includeAttribute))
|
||||
{
|
||||
if (includeAttribute.Value.Contains("ScriptCore.csproj", true))
|
||||
if (includeAttribute.Value.Contains(projectName, true))
|
||||
{
|
||||
scriptCoreProjectReference = projectNode;
|
||||
projectReference = projectNode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool changedContent = false;
|
||||
|
||||
if (referenceScriptCoreProject)
|
||||
{
|
||||
Log.EngineLogger.Info("Debug mode enabled. Referencing ScriptCore.csproj in Script-Solution.");
|
||||
Log.EngineLogger.Info($"Debug mode enabled. Referencing {projectName} in Script-Solution.");
|
||||
|
||||
if (scriptCoreProjectReference == null)
|
||||
if (projectReference == null)
|
||||
{
|
||||
Log.EngineLogger.Warning("No reference to ScriptCore.csproj found. Adding...");
|
||||
scriptCoreProjectReference = solution.DocumentElement.AddChild("Project");
|
||||
Log.EngineLogger.Warning($"No reference to {projectName} found. Adding...");
|
||||
projectReference = solution.DocumentElement.AddChild("Project");
|
||||
|
||||
changedContent = true;
|
||||
}
|
||||
|
||||
String projectFilePath = scope .();
|
||||
GetScriptCoreProjectFilePath(projectFilePath);
|
||||
scriptCoreProjectReference["Path"] = projectFilePath;
|
||||
if (projectReference["Path"] != projectFilePath)
|
||||
{
|
||||
projectReference["Path"] = projectFilePath;
|
||||
changedContent = true;
|
||||
}
|
||||
}
|
||||
else if (scriptCoreProjectReference != null)
|
||||
else if (projectReference != null)
|
||||
{
|
||||
Log.EngineLogger.Warning("Reference to ScriptCore.csproj found. Removing...");
|
||||
scriptCoreProjectReference.RemoveFromParent(true);
|
||||
}
|
||||
|
||||
String modifiedSolution = new String(solutionFileContent.Length * 2);
|
||||
defer delete modifiedSolution;
|
||||
solution.SaveToString(modifiedSolution);
|
||||
|
||||
if (File.WriteAllText(solutionPath, modifiedSolution) case .Err)
|
||||
{
|
||||
Log.EngineLogger.Error($"Failed to modify {solutionPath}");
|
||||
return .Err;
|
||||
Log.EngineLogger.Warning($"Reference to {projectName} found. Removing...");
|
||||
projectReference.RemoveFromParent(true);
|
||||
changedContent = true;
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
return changedContent;
|
||||
}
|
||||
|
||||
|
||||
/// If necessary adds or removes the reference to ScriptCore.dll from the project (.csproj) file.
|
||||
private Result<void> FixupScriptProjectFile(bool referenceScriptCoreProject)
|
||||
{
|
||||
@@ -372,8 +439,19 @@ class Project
|
||||
}
|
||||
}
|
||||
|
||||
bool changedContent = false;
|
||||
|
||||
// Try to find a reference to the dll
|
||||
XmlNode dllReferenceNode = project.DocumentElement.Find("Reference", "Include", "ScriptCore", recursive: true);
|
||||
|
||||
mixin SetIfDifferent(XmlNode node, StringView attributeName, StringView wantedValue)
|
||||
{
|
||||
if (node.GetAttribute(attributeName)?.Value != wantedValue)
|
||||
{
|
||||
node.SetAttribute(attributeName, wantedValue);
|
||||
changedContent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (referenceScriptCoreProject)
|
||||
{
|
||||
@@ -383,6 +461,7 @@ class Project
|
||||
{
|
||||
Log.EngineLogger.Warning("Reference to ScriptCore.dll found. Removing...");
|
||||
dllReferenceNode.RemoveFromParent(true);
|
||||
changedContent = true;
|
||||
}
|
||||
|
||||
if (scriptCoreProjectReference == null)
|
||||
@@ -392,16 +471,21 @@ class Project
|
||||
XmlNode itemGroup = project.DocumentElement.Find("ItemGroup", recursive: true);
|
||||
|
||||
scriptCoreProjectReference = itemGroup.AddChild("ProjectReference");
|
||||
|
||||
String scriptCoreProjectPath = scope .();
|
||||
Directory.GetCurrentDirectory(scriptCoreProjectPath);
|
||||
Path.Combine(scriptCoreProjectPath, "../ScriptCore/ScriptCore.csproj");
|
||||
Path.ToActualPath(scriptCoreProjectPath);
|
||||
|
||||
scriptCoreProjectReference.SetAttribute("Include", scriptCoreProjectPath);
|
||||
scriptCoreProjectReference.SetAttribute("OutputItemType", "Analyzer");
|
||||
scriptCoreProjectReference.SetAttribute("ReferenceOutputAssembly", "true");
|
||||
changedContent = true;
|
||||
}
|
||||
|
||||
String scriptCoreProjectPath = scope .();
|
||||
Directory.GetCurrentDirectory(scriptCoreProjectPath);
|
||||
Path.Combine(scriptCoreProjectPath, "../ScriptCore/ScriptCore.csproj");
|
||||
Path.ToActualPath(scriptCoreProjectPath);
|
||||
|
||||
if (!Path.Equals(scriptCoreProjectReference.GetAttribute("Include")?.Value, scriptCoreProjectPath))
|
||||
{
|
||||
scriptCoreProjectReference.SetAttribute("Include", scriptCoreProjectPath);
|
||||
changedContent = true;
|
||||
}
|
||||
SetIfDifferent!(scriptCoreProjectReference, "OutputItemType", "Analyzer");
|
||||
SetIfDifferent!(scriptCoreProjectReference, "ReferenceOutputAssembly", "true");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -409,6 +493,7 @@ class Project
|
||||
{
|
||||
Log.EngineLogger.Warning("Reference to ScriptCore.csproj found. Removing...");
|
||||
scriptCoreProjectReference.RemoveFromParent(true);
|
||||
changedContent = true;
|
||||
}
|
||||
|
||||
if (dllReferenceNode == null)
|
||||
@@ -418,16 +503,18 @@ class Project
|
||||
XmlNode itemGroup = project.DocumentElement.Find("ItemGroup", recursive: true);
|
||||
|
||||
dllReferenceNode = itemGroup.AddChild("Reference");
|
||||
changedContent = true;
|
||||
}
|
||||
|
||||
dllReferenceNode.SetAttribute("Include", "ScriptCore");
|
||||
dllReferenceNode.SetAttribute("OutputItemType", "Analyzer");
|
||||
dllReferenceNode.SetAttribute("ReferenceOutputAssembly", "true");
|
||||
|
||||
SetIfDifferent!(dllReferenceNode, "Include", "ScriptCore");
|
||||
SetIfDifferent!(dllReferenceNode, "OutputItemType", "Analyzer");
|
||||
SetIfDifferent!(dllReferenceNode, "ReferenceOutputAssembly", "true");
|
||||
|
||||
XmlNode hintPathNode = dllReferenceNode.Find("HintPath");
|
||||
if (hintPathNode == null)
|
||||
{
|
||||
hintPathNode = dllReferenceNode.AddChild("HintPath");
|
||||
changedContent = true;
|
||||
}
|
||||
|
||||
String pathToScriptCoreDll = scope .();
|
||||
@@ -441,16 +528,29 @@ class Project
|
||||
return .Err;
|
||||
}
|
||||
|
||||
hintPathNode.SetText(pathToScriptCoreDll);
|
||||
if (!Path.Equals(hintPathNode.Text, pathToScriptCoreDll))
|
||||
{
|
||||
hintPathNode.SetText(pathToScriptCoreDll);
|
||||
changedContent = true;
|
||||
}
|
||||
}
|
||||
|
||||
String modfiedProject = new String(projectFileContent.Length * 2);
|
||||
defer delete modfiedProject;
|
||||
project.SaveToString(modfiedProject);
|
||||
|
||||
if (File.WriteAllText(csprojPath, modfiedProject) case .Err)
|
||||
|
||||
if (changedContent)
|
||||
{
|
||||
Log.EngineLogger.Error($"Failed to modify {csprojPath}");
|
||||
Log.EngineLogger.Info("Script project file updated, saving...");
|
||||
|
||||
String modfiedProject = new String(projectFileContent.Length * 2);
|
||||
defer delete modfiedProject;
|
||||
project.SaveToString(modfiedProject);
|
||||
|
||||
if (File.WriteAllText(csprojPath, modfiedProject) case .Err)
|
||||
{
|
||||
Log.EngineLogger.Error($"Failed to modify {csprojPath}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.EngineLogger.Info("Script project file already up to date.");
|
||||
}
|
||||
|
||||
return .Ok;
|
||||
|
||||
@@ -375,17 +375,25 @@ class EditorSettings
|
||||
if (_recentProjects == null)
|
||||
_recentProjects = new List<String>();
|
||||
|
||||
String absolutePath = new String(value.Length);
|
||||
Path.GetFullPath(value, absolutePath);
|
||||
if (absolutePath.EndsWith(Path.DirectorySeparatorChar))
|
||||
absolutePath.RemoveFromEnd(1);
|
||||
|
||||
// Remove duplicates
|
||||
for (var entry in _recentProjects)
|
||||
{
|
||||
if (entry == value)
|
||||
if (entry.EndsWith(Path.DirectorySeparatorChar))
|
||||
entry.RemoveFromEnd(1);
|
||||
|
||||
if (Path.Equals(entry, absolutePath))
|
||||
{
|
||||
delete entry;
|
||||
@entry.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
_recentProjects.Insert(0, new String(value));
|
||||
_recentProjects.Insert(0, absolutePath);
|
||||
|
||||
if (_recentProjects.Count > 10)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace System.Collections;
|
||||
|
||||
extension List<T>
|
||||
{
|
||||
public bool Equals(List<T> other)
|
||||
{
|
||||
if (this === other)
|
||||
return true;
|
||||
|
||||
if (this.Count != other.Count)
|
||||
return false;
|
||||
|
||||
for (int i < this.Count)
|
||||
{
|
||||
if (this[i] != other[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,9 @@ static class CoreClrHelper
|
||||
|
||||
//static function void(char8* path) LoadScriptAssembly;
|
||||
static LoadScriptAssemblyFunc _loadScriptAssembly;
|
||||
static function void() _unloadAssemblies;
|
||||
static function void() _unloadScriptAssembly;
|
||||
|
||||
static function void() _waitUntilOldAssemblyDead;
|
||||
|
||||
// public static unsafe void GetScriptClasses(void** outBuffer, long* length)
|
||||
private function void GetScriptClassesFunc(void** outBuffer, int64* length);
|
||||
@@ -108,8 +110,10 @@ static class CoreClrHelper
|
||||
{
|
||||
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "LoadScriptAssembly", out _loadScriptAssembly);
|
||||
|
||||
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadAssemblies", out _unloadAssemblies);
|
||||
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "UnloadScriptAssembly", out _unloadScriptAssembly);
|
||||
|
||||
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "WaitUntilOldAssemblyDead", out _waitUntilOldAssemblyDead);
|
||||
|
||||
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "GetScriptClasses", out _getScriptClasses);
|
||||
|
||||
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "FreeScriptClassNames", out _freeScriptClassNames);
|
||||
@@ -130,16 +134,21 @@ static class CoreClrHelper
|
||||
GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "InvokeEntityOnCollisionEnter2D", out _entityScriptFunctions.OnCollisionEnter2D);
|
||||
}
|
||||
|
||||
public static void LoadAppAssembly(Span<uint8> appAssemblyData, Span<uint8> pdbData)
|
||||
public static void LoadScriptAssembly(Span<uint8> appAssemblyData, Span<uint8> pdbData)
|
||||
{
|
||||
_loadScriptAssembly(appAssemblyData.Ptr, appAssemblyData.Length, pdbData.Ptr, pdbData.Length);
|
||||
}
|
||||
|
||||
public static void UnloadAssemblies()
|
||||
public static void UnloadScriptAssembly()
|
||||
{
|
||||
_unloadAssemblies();
|
||||
_unloadScriptAssembly();
|
||||
}
|
||||
|
||||
public static void WaitUntilOldAssemblyDead()
|
||||
{
|
||||
_waitUntilOldAssemblyDead();
|
||||
}
|
||||
|
||||
/// Gets an array of script class infos. Use FreeScriptClassNames to release the buffer.
|
||||
public static void GetScriptClasses(out void* outBuffer, out int64 entryCount)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ class EntitySerializerWrapper : NewScriptClass
|
||||
function void DeserializeMethod(UUID entityId, void* serializedObjPtr, void* serializerPtr);
|
||||
function void SerializeStaticMethod(char8* fullTypeName, void* serializedObjPtr, void* serializerPtr);
|
||||
function void DeserializeStaticMethod(char8* fullTypeName, void* serializedObjPtr, void* serializerPtr);
|
||||
function void FinishSerializationMethod(void* serializerPtr);
|
||||
|
||||
private CreateSerializationContextMethod _createSerializationContextMethod;
|
||||
//private ClearSerializationContextMethod _clearSerializationContextMethod;
|
||||
@@ -25,16 +26,19 @@ class EntitySerializerWrapper : NewScriptClass
|
||||
private DeserializeMethod _deserializeMethod;
|
||||
private SerializeStaticMethod _serializeStaticMethod;
|
||||
private DeserializeStaticMethod _deserializeStaticMethod;
|
||||
private FinishSerializationMethod _finishSerializationMethod;
|
||||
|
||||
[AllowAppend]
|
||||
public this() : base("GlitchyEngine.Serialization.EntitySerializer", .Empty, .None, false)
|
||||
{
|
||||
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "CreateSerializationContext", out _createSerializationContextMethod);
|
||||
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "DestroySerializationContext", out _destroySerializationContextMethod);
|
||||
|
||||
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_Serialize", out _serializeMethod);
|
||||
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_Deserialize", out _deserializeMethod);
|
||||
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_SerializeStaticFields", out _serializeStaticMethod);
|
||||
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_DeserializeStaticFields", out _deserializeStaticMethod);
|
||||
CoreClrHelper.GetFunctionPointerUnmanagedCallersOnly("GlitchyEngine.ScriptGlue, ScriptCore", "EntitySerializer_FinishSerialization", out _finishSerializationMethod);
|
||||
}
|
||||
|
||||
public void CreateSerializationContext(ScriptInstanceSerializer serializer)
|
||||
@@ -93,4 +97,10 @@ class EntitySerializerWrapper : NewScriptClass
|
||||
|
||||
_deserializeStaticMethod(@class.FullName.CStr(), serializedObjectPtr, serializerPtr);
|
||||
}
|
||||
|
||||
public void FinishSerialization(ScriptInstanceSerializer serializer)
|
||||
{
|
||||
void* serializerPtr = Internal.UnsafeCastToPtr(serializer);
|
||||
_finishSerializationMethod(serializerPtr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,8 @@ class EngineClasses
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Init after FileSystemWatcher so that ScriptEngine's destructor runs before FSW's. The proper fix might be to not delete _userAssemblyWatcher in the static destructor...
|
||||
[StaticInitAfter(typeof(FileSystemWatcher))]
|
||||
static class ScriptEngine
|
||||
{
|
||||
// TODO: Maybe make it possible to set the path to ScriptCore just like for the script project?
|
||||
@@ -190,11 +192,11 @@ static class ScriptEngine
|
||||
}
|
||||
|
||||
/// Changes the path to the current app assembly.
|
||||
public static void SetAppAssemblyPath(StringView appAssemblyPath)
|
||||
public static void SetAppAssemblyPath(StringView appAssemblyPath, bool retainScriptData)
|
||||
{
|
||||
_appAssemblyPath.Set(appAssemblyPath);
|
||||
|
||||
ReloadAssemblies();
|
||||
ReloadAssemblies(retainScriptData);
|
||||
}
|
||||
|
||||
static void InitRuntime()
|
||||
@@ -207,32 +209,6 @@ static class ScriptEngine
|
||||
CoreClrHelper.Init(ScriptCorePath);
|
||||
}
|
||||
|
||||
/*static void InitMono()
|
||||
{
|
||||
Mono.mono_set_assemblies_path("mono/lib/4.5");
|
||||
|
||||
if (_debuggingEnabled)
|
||||
{
|
||||
char8*[2] options = .(
|
||||
"--debugger-agent=transport=dt_socket,address=127.0.0.1:2550,server=y,suspend=n,loglevel=3,logfile=MonoDebugger.log",
|
||||
"--soft-breakpoints"
|
||||
);
|
||||
|
||||
Mono.mono_jit_parse_options(options.Count, &options);
|
||||
Mono.mono_debug_init(.Mono);
|
||||
}
|
||||
|
||||
s_RootDomain = Mono.mono_jit_init("GlitchyEngineJITRuntime");
|
||||
Log.EngineLogger.Assert(s_RootDomain != null, "Failed to initialize mono root domain");
|
||||
|
||||
if (_debuggingEnabled)
|
||||
{
|
||||
Mono.mono_debug_domain_create(s_RootDomain);
|
||||
}
|
||||
|
||||
Mono.mono_thread_set_main(Mono.mono_thread_current());
|
||||
}*/
|
||||
|
||||
static bool _requestingReload = false;
|
||||
|
||||
static void InitAssemblyWatcher()
|
||||
@@ -263,7 +239,7 @@ static class ScriptEngine
|
||||
|
||||
Application.Instance.InvokeOnMainThread(new () =>
|
||||
{
|
||||
ReloadAssemblies();
|
||||
ReloadAssemblies(retainScriptData: true);
|
||||
|
||||
_requestingReload = false;
|
||||
_userAssemblyWatcher.StartRaisingEvents();
|
||||
@@ -299,23 +275,27 @@ static class ScriptEngine
|
||||
|
||||
File.ReadAll(pdbPath, pdbData);
|
||||
|
||||
CoreClrHelper.LoadAppAssembly(data, pdbData);
|
||||
CoreClrHelper.LoadScriptAssembly(data, pdbData);
|
||||
|
||||
GetEntitiesFromAssemblies();
|
||||
|
||||
//ScriptGlue.RegisterManagedComponents();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.EngineLogger.Error("Failed to load script assembly, because it doesn't exist.");
|
||||
}
|
||||
|
||||
InitAssemblyWatcher();
|
||||
}
|
||||
|
||||
/// Starts the script runtime and sets the context scene.
|
||||
public static void StartRuntime(Scene context)
|
||||
public static void StartRuntime(Scene context, bool retainScriptData = true)
|
||||
{
|
||||
Debug.Assert(s_Context == null, "StartRuntime was called twice without StopRuntime in between!");
|
||||
Context = context;
|
||||
|
||||
ReloadAssemblies();
|
||||
ReloadAssemblies(retainScriptData);
|
||||
}
|
||||
|
||||
/// Stopts the script runtime and disposes of all script instances.
|
||||
@@ -435,18 +415,23 @@ static class ScriptEngine
|
||||
CoreClrHelper.FreeScriptClassNames();
|
||||
}
|
||||
|
||||
public static void ReloadAssemblies()
|
||||
public static void ReloadAssemblies(bool retainScriptData = true)
|
||||
{
|
||||
Debug.Profiler.ProfileFunction!();
|
||||
|
||||
// TODO: Assembly reload might actually take a second. We should consider doing it in a separate thread in the editor.
|
||||
|
||||
Log.EngineLogger.Info("Reloading script assemblies.");
|
||||
|
||||
ScriptInstanceSerializer contextSerializer = null;
|
||||
|
||||
ScriptInstanceSerializer contextSerializer = scope .();
|
||||
|
||||
contextSerializer.SerializeScriptInstances();
|
||||
|
||||
// TODO: Unload script assemblies, remove class handles, fire unload events?
|
||||
if (retainScriptData)
|
||||
{
|
||||
contextSerializer = scope:: .();
|
||||
contextSerializer.SerializeScriptInstances();
|
||||
}
|
||||
|
||||
UnloadScriptAssemblies();
|
||||
|
||||
LoadScriptAssemblies();
|
||||
|
||||
@@ -467,21 +452,37 @@ static class ScriptEngine
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contextSerializer.DeserializeScriptInstances();
|
||||
|
||||
if (retainScriptData)
|
||||
{
|
||||
contextSerializer.DeserializeScriptInstances();
|
||||
}
|
||||
|
||||
Log.EngineLogger.Info("Script assemblies reloaded!");
|
||||
}
|
||||
|
||||
private static void UnloadScriptAssemblies()
|
||||
{
|
||||
for (let (id, scriptInstance) in _entityScriptInstances)
|
||||
{
|
||||
if (Context.GetEntityByID(id) case .Ok(let entity))
|
||||
{
|
||||
ScriptComponent* script = entity.GetComponent<ScriptComponent>();
|
||||
DestroyInstance(entity, script);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.EngineLogger.AssertDebug(false, "Entity script instance exists but no entity?!");
|
||||
}
|
||||
}
|
||||
|
||||
CoreClrHelper.UnloadScriptAssembly();
|
||||
CoreClrHelper.WaitUntilOldAssemblyDead();
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
/*Mono.mono_domain_set(s_RootDomain, false);
|
||||
|
||||
Mono.mono_domain_unload(s_AppDomain);
|
||||
s_AppDomain = null;
|
||||
|
||||
Mono.mono_jit_cleanup(s_RootDomain);
|
||||
s_RootDomain = null;*/
|
||||
UnloadScriptAssemblies();
|
||||
}
|
||||
|
||||
/// Returns the script class with the given name, or null, if no such class exists.
|
||||
@@ -492,19 +493,17 @@ static class ScriptEngine
|
||||
return scriptClass;
|
||||
}
|
||||
|
||||
internal static void LogScriptException(ScriptException exception, UUID entityId)
|
||||
internal static void LogScriptException(ScriptException exception)
|
||||
{
|
||||
String entityInfo = scope .();
|
||||
|
||||
if (entityId != .Zero && Context != null)
|
||||
if (exception.EntityId != .Zero && Context != null)
|
||||
{
|
||||
exception.EntityId = entityId;
|
||||
|
||||
Result<Entity> sourceEntity = Context.GetEntityByID(entityId);
|
||||
Result<Entity> sourceEntity = Context.GetEntityByID(exception.EntityId);
|
||||
|
||||
if (sourceEntity case .Ok(let e))
|
||||
{
|
||||
entityInfo.AppendF($" ({e.Name} | {entityId})");
|
||||
entityInfo.AppendF($" ({e.Name} | {exception.EntityId})");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,107 @@
|
||||
using System;
|
||||
using GlitchyEngine.Core;
|
||||
using System.Collections;
|
||||
|
||||
namespace GlitchyEngine.Scripting;
|
||||
|
||||
|
||||
public class ScriptException
|
||||
{
|
||||
private String _fullName ~ delete:append _;
|
||||
|
||||
private String _message ~ delete:append _;
|
||||
|
||||
private String _stackTrace ~ delete:append _;
|
||||
/// The clean stack trace only contains the Managed Stack (the full trace contains one line for the native-to-managed entry)
|
||||
private StringView _cleanStackTrace;
|
||||
|
||||
//TODO
|
||||
//private ScriptException _innerException ~ _?.ReleaseRef();
|
||||
private StackTrace _stackTrace ~ delete _;
|
||||
|
||||
public StringView FullName => _fullName;
|
||||
public StringView Message => _message;
|
||||
|
||||
public StringView StackTrace => _stackTrace;
|
||||
public StringView CleanStackTrace => _cleanStackTrace;
|
||||
public StackTrace StackTrace => _stackTrace;
|
||||
|
||||
//public ScriptException InnerException => _innerException;
|
||||
|
||||
public UUID EntityId { get; set; }
|
||||
public UUID EntityId { get; private set; }
|
||||
|
||||
[AllowAppend]
|
||||
public this(UUID entityId, StringView fullExceptionClassName, StringView message, StringView stackTrace)
|
||||
public this(UUID entityId, StringView fullExceptionClassName, StringView message, StackTrace ownStackTrace)
|
||||
{
|
||||
String allocFullExceptionClassName = append String(fullExceptionClassName);
|
||||
String allocMessage = append String(message);
|
||||
String allocStackTrace = append String(stackTrace);
|
||||
|
||||
EntityId = entityId;
|
||||
|
||||
_stackTrace = ownStackTrace;
|
||||
|
||||
_fullName = allocFullExceptionClassName;
|
||||
_message = allocMessage;
|
||||
_stackTrace = allocStackTrace;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
_cleanStackTrace = _stackTrace;
|
||||
public class StackFrameInfo
|
||||
{
|
||||
private String _fileName ~ delete:append _;
|
||||
private String _methodSignature ~ delete:append _;
|
||||
public int Line;
|
||||
public int Column;
|
||||
|
||||
public StringView FileName => _fileName;
|
||||
|
||||
public StringView MethodSignature => _methodSignature;
|
||||
|
||||
[AllowAppend]
|
||||
public this(StringView fileName, StringView methodSignature, int line, int column)
|
||||
{
|
||||
String allocFileName = append String(fileName);
|
||||
String allocMethodSignature = append String(methodSignature);
|
||||
|
||||
_fileName = allocFileName;
|
||||
_methodSignature = allocMethodSignature;
|
||||
|
||||
Line = line;
|
||||
Column = column;
|
||||
}
|
||||
|
||||
public override void ToString(String outBuffer)
|
||||
{
|
||||
outBuffer.Append("at ");
|
||||
|
||||
if (MethodSignature.IsEmpty)
|
||||
outBuffer.Append("<unknown>");
|
||||
else
|
||||
outBuffer.Append(MethodSignature);
|
||||
|
||||
outBuffer.Append(" in ");
|
||||
|
||||
if (!_fileName.IsEmpty)
|
||||
outBuffer.AppendF($"{FileName}, line {Line}:{Column}");
|
||||
else
|
||||
outBuffer.Append("<unknown>");
|
||||
}
|
||||
}
|
||||
|
||||
public class StackTrace
|
||||
{
|
||||
private List<StackFrameInfo> _stackFrameInfos ~ DeleteContainerAndItems!(_);
|
||||
|
||||
public Span<StackFrameInfo> Frames => _stackFrameInfos;
|
||||
|
||||
public this(List<StackFrameInfo> ownStackFrameInfos)
|
||||
{
|
||||
_stackFrameInfos = ownStackFrameInfos;
|
||||
}
|
||||
|
||||
[Commutable]
|
||||
public static bool operator ==(StackTrace a, StackTrace b)
|
||||
{
|
||||
if (a == null || b == null)
|
||||
return a === b;
|
||||
|
||||
return a._stackFrameInfos.Equals(b._stackFrameInfos);
|
||||
}
|
||||
|
||||
public override void ToString(String outBuffer)
|
||||
{
|
||||
for (let frame in _stackFrameInfos)
|
||||
{
|
||||
frame.ToString(outBuffer);
|
||||
outBuffer.Append('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,19 +28,29 @@ using internal GlitchyEngine.Content;
|
||||
// TODO: Move to logger?
|
||||
class MessageOrigin
|
||||
{
|
||||
private UUID _entityId;
|
||||
private String _fileName ~ delete:append _;
|
||||
private String _callerMemberName ~ delete:append _;
|
||||
private int _lineNumber;
|
||||
private int _columnNumber;
|
||||
|
||||
public UUID EntityId => _entityId;
|
||||
public StringView FileName => _fileName;
|
||||
public StringView CallerMemberName => _callerMemberName;
|
||||
public int LineNumber => _lineNumber;
|
||||
public int ColumnNumber => _columnNumber;
|
||||
|
||||
[AllowAppend]
|
||||
public this(StringView fileName, int lineNumber)
|
||||
public this(UUID entityId, StringView fileName, StringView callerMemberName, int lineNumber, int columnNumber)
|
||||
{
|
||||
String file = append String(fileName);
|
||||
String caller = append String(callerMemberName);
|
||||
|
||||
_entityId = entityId;
|
||||
_fileName = file;
|
||||
_callerMemberName = caller;
|
||||
_lineNumber = lineNumber;
|
||||
_columnNumber = columnNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +86,7 @@ struct GlueParamAttribute : Attribute
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects all methods in ScriptGlue that have a RegisterCallAttribute. Generates a function pointer for them as well as outputting a json file that can be used to generate the script library side of the ScriptGlue construct.
|
||||
[AttributeUsage(.Struct)]
|
||||
struct EngineFunctionsGeneratorAttribute : Attribute, IComptimeTypeApply
|
||||
{
|
||||
@@ -185,11 +196,13 @@ struct EngineFunctionsGeneratorAttribute : Attribute, IComptimeTypeApply
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct containing all pointers to functions that can be called from the script library.
|
||||
[EngineFunctionsGenerator]
|
||||
struct EngineFunctions
|
||||
{
|
||||
static Self _functions;
|
||||
|
||||
|
||||
/// Sends the references to all ScriptGlue functions to the script library.
|
||||
[RegisterMethod]
|
||||
public static void FillEngineFunctions()
|
||||
{
|
||||
@@ -200,7 +213,7 @@ struct EngineFunctions
|
||||
}
|
||||
}
|
||||
|
||||
/* Adding this attribute to a method will log method entry and returned Result<T> errors */
|
||||
/// Collects all methods in ScriptGlue that have a RegisterCallAttribute. Generates the assignment of the corresponding function pointer in EngineFunctions.
|
||||
[AttributeUsage(.Method | .Constructor)]
|
||||
public struct RegisterMethodAttribute : Attribute, IOnMethodInit
|
||||
{
|
||||
@@ -252,6 +265,7 @@ static class ScriptGlue
|
||||
|
||||
private static String _lastExceptionMessage = new .() ~ delete _;
|
||||
|
||||
/// Initializes the glue layer between native engine and script library.
|
||||
public static void Init()
|
||||
{
|
||||
if (_setEngineFunctions == null)
|
||||
@@ -264,6 +278,13 @@ static class ScriptGlue
|
||||
RegisterManagedComponents();
|
||||
}
|
||||
|
||||
/// Collects and sends the function pointers to all glue functions to the script library.
|
||||
private static void RegisterCalls()
|
||||
{
|
||||
EngineFunctions.FillEngineFunctions();
|
||||
}
|
||||
|
||||
/// Creates the function pointers to create, remove and check for components and sends them to the script library.
|
||||
public static void RegisterManagedComponents()
|
||||
{
|
||||
Debug.Profiler.ProfileFunction!();
|
||||
@@ -278,11 +299,7 @@ static class ScriptGlue
|
||||
RegisterComponent<MeshRendererComponent>();
|
||||
}
|
||||
|
||||
private static void RegisterCalls()
|
||||
{
|
||||
EngineFunctions.FillEngineFunctions();
|
||||
}
|
||||
|
||||
/// Register the function pointers to create, remove and check for components of the specified type and sends them to the script library.
|
||||
private static void RegisterComponent<T>() where T : struct, new
|
||||
{
|
||||
String fullComponentTypeName = scope String();
|
||||
@@ -445,7 +462,7 @@ static class ScriptGlue
|
||||
#region Log
|
||||
|
||||
[RegisterCall, CallingConvention(.Cdecl)]
|
||||
static void Log_LogMessage(LogLevel logLevel, StringView messagePtr, StringView fileNamePtr, int lineNumber)
|
||||
static void Log_LogMessage(LogLevel logLevel, UUID entityId, StringView messagePtr, StringView fileNamePtr, StringView callerMemberNamePtr, int lineNumber, int columnNumber)
|
||||
{
|
||||
String escapedMessage = new:ScopedAlloc! String(messagePtr);
|
||||
|
||||
@@ -455,9 +472,7 @@ static class ScriptGlue
|
||||
|
||||
if (!fileNamePtr.IsEmpty)
|
||||
{
|
||||
String fileName = new:ScopedAlloc! String(fileNamePtr);
|
||||
|
||||
MessageOrigin messageOrigin = scope MessageOrigin(fileName, lineNumber);
|
||||
MessageOrigin messageOrigin = scope MessageOrigin(entityId, fileNamePtr, callerMemberNamePtr, lineNumber, columnNumber);
|
||||
|
||||
Log.ClientLogger.Log((LogLevel)logLevel, escapedMessage, messageOrigin);
|
||||
}
|
||||
@@ -467,11 +482,27 @@ static class ScriptGlue
|
||||
}
|
||||
}
|
||||
|
||||
[RegisterCall, CallingConvention(.Cdecl)]
|
||||
static void Log_LogException(UUID entityId, StringView fullExceptionClassName, StringView exceptionMessage, StringView stackTrace)
|
||||
[Ordered]
|
||||
public struct GlueStackFrameInfo
|
||||
{
|
||||
ScriptException exception = new ScriptException(entityId, fullExceptionClassName, exceptionMessage, stackTrace);
|
||||
ScriptEngine.LogScriptException(exception, entityId);
|
||||
public StringView FileName;
|
||||
public StringView MethodSignature;
|
||||
public int32 Line;
|
||||
public int32 Column;
|
||||
}
|
||||
|
||||
[RegisterCall, CallingConvention(.Cdecl)]
|
||||
static void Log_LogException(UUID entityId, StringView fullExceptionClassName, StringView exceptionMessage, GlueStackFrameInfo* stackFrameInfos, int32 stackFrameCount)
|
||||
{
|
||||
List<StackFrameInfo> frames = new .(stackFrameCount);
|
||||
|
||||
for (GlueStackFrameInfo frameInfo in Span<GlueStackFrameInfo>(stackFrameInfos, stackFrameCount))
|
||||
{
|
||||
frames.Add(new StackFrameInfo(frameInfo.FileName, frameInfo.MethodSignature, frameInfo.Line, frameInfo.Column));
|
||||
}
|
||||
|
||||
ScriptException exception = new ScriptException(entityId, fullExceptionClassName, exceptionMessage, new StackTrace(frames));
|
||||
ScriptEngine.LogScriptException(exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -642,7 +673,7 @@ static class ScriptGlue
|
||||
|
||||
#endregion
|
||||
|
||||
#region TransformComponen
|
||||
#region TransformComponent
|
||||
|
||||
[RegisterCall, CallingConvention(.Cdecl)]
|
||||
static EngineResult Transform_GetParent(UUID entityId, out UUID parentId)
|
||||
@@ -966,7 +997,7 @@ static class ScriptGlue
|
||||
|
||||
#endregion Rigidbody2D
|
||||
|
||||
#region Camer
|
||||
#region Camera
|
||||
|
||||
[RegisterCall, CallingConvention(.Cdecl)]
|
||||
static void Camera_GetProjectionType(UUID entityId, out SceneCamera.ProjectionType projectionType)
|
||||
@@ -1534,6 +1565,7 @@ static class ScriptGlue
|
||||
asset
|
||||
}*/
|
||||
|
||||
/// Set an exception message that the script library can collect when a glue function returned an error.
|
||||
static void SetExceptionMessage(StringView message)
|
||||
{
|
||||
_lastExceptionMessage.Set(message);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections;
|
||||
using GlitchyEngine.Core;
|
||||
using GlitchyEngine.Serialization;
|
||||
using System;
|
||||
|
||||
namespace GlitchyEngine.Scripting;
|
||||
|
||||
@@ -31,11 +32,22 @@ public class ScriptInstanceSerializer
|
||||
ScriptEngine.Classes.EntitySerializer.CreateSerializationContext(this);
|
||||
}
|
||||
|
||||
/// Signals, that no more objects will be serialized into this context.
|
||||
/// This allows the managed serialization context to do cleanup:
|
||||
/// i. e. for C# we can release references to types which allows us to cleanup the old script assembly.
|
||||
public void FinishSerialization()
|
||||
{
|
||||
ScriptEngine.Classes.EntitySerializer.FinishSerialization(this);
|
||||
}
|
||||
|
||||
|
||||
/// Serializes all script instances that are currently managed by the ScriptEngine.
|
||||
public void SerializeScriptInstances()
|
||||
{
|
||||
Debug.Profiler.ProfileFunction!();
|
||||
|
||||
Log.EngineLogger.Trace($"Serializing {Internal.UnsafeCastToPtr(this)}...");
|
||||
|
||||
Init();
|
||||
|
||||
for (let (id, scriptInstance) in ScriptEngine._entityScriptInstances)
|
||||
@@ -47,6 +59,8 @@ public class ScriptInstanceSerializer
|
||||
{
|
||||
SerializeStaticScriptClassFields(scriptClass);
|
||||
}
|
||||
|
||||
FinishSerialization();
|
||||
}
|
||||
|
||||
/// Serializes the given script instance.
|
||||
@@ -67,6 +81,8 @@ public class ScriptInstanceSerializer
|
||||
public void DeserializeScriptInstances()
|
||||
{
|
||||
Debug.Profiler.ProfileFunction!();
|
||||
|
||||
Log.EngineLogger.Trace($"Deserializing {Internal.UnsafeCastToPtr(this)}...");
|
||||
|
||||
Init();
|
||||
|
||||
@@ -79,6 +95,8 @@ public class ScriptInstanceSerializer
|
||||
{
|
||||
DeserializeStaticScriptClassFields(scriptClass);
|
||||
}
|
||||
|
||||
FinishSerialization();
|
||||
}
|
||||
|
||||
/// Deserializes the data into the given script instance, if there is data available.
|
||||
|
||||
@@ -324,7 +324,7 @@ class SerializedObject
|
||||
Serialize.Value(writer, ValueView(typeof(StringView), &dataView), environment);
|
||||
case .EngineObjectReference:
|
||||
writer.Type("EngineObject");
|
||||
if (field.Data.EngineObject.FullTypeName.IsEmpty)
|
||||
if (!((StringView)field.Data.ListEngineObject.FullTypeName).IsEmpty)
|
||||
writer.Type((StringView)field.Data.ListEngineObject.FullTypeName);
|
||||
Serialize.Value(writer, field.Data.EngineObject.ID, environment);
|
||||
case .ObjectReference:
|
||||
|
||||
@@ -293,7 +293,8 @@ namespace GlitchyEngine.World
|
||||
|
||||
private void StartRuntime()
|
||||
{
|
||||
ScriptEngine.StartRuntime(this);
|
||||
// We don't retain data of script instances, because our callers do that if necessary
|
||||
ScriptEngine.StartRuntime(this, retainScriptData: false);
|
||||
|
||||
// Initialize ScriptComponents
|
||||
for (let (handle, scriptComponent) in _ecsWorld.Enumerate<ScriptComponent>())
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
using System;
|
||||
using GlitchyEngine.Core;
|
||||
using GlitchyEngine.Extensions;
|
||||
using GlitchyEngine.Graphics;
|
||||
using GlitchyEngine.Math;
|
||||
using GlitchyEngine.Math.Attributes;
|
||||
using ImGuiNET;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
@@ -10,12 +17,7 @@ using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using GlitchyEngine.Core;
|
||||
using GlitchyEngine.Extensions;
|
||||
using GlitchyEngine.Graphics;
|
||||
using GlitchyEngine.Math;
|
||||
using GlitchyEngine.Math.Attributes;
|
||||
using ImGuiNET;
|
||||
using System.Text.Unicode;
|
||||
using Component = GlitchyEngine.Core.Component;
|
||||
|
||||
namespace GlitchyEngine.Editor;
|
||||
@@ -282,10 +284,10 @@ internal class EntityEditor
|
||||
if (value is float || value is double || value is Half)
|
||||
format = "0.#####";
|
||||
else
|
||||
format = "0";
|
||||
format = null;
|
||||
}
|
||||
|
||||
if (!format.StartsWith("%"))
|
||||
if (format?.StartsWith("%") == false)
|
||||
{
|
||||
if (value is float f)
|
||||
{
|
||||
@@ -303,14 +305,14 @@ internal class EntityEditor
|
||||
|
||||
if (range?.Slider == true)
|
||||
{
|
||||
if (ImGui.SliderScalar(fieldId, dataType, (IntPtr)(&value), (IntPtr)(&min), (IntPtr)(&max), format))
|
||||
if (ImGui.SliderScalar(fieldId, dataType, (IntPtr)(&value), (IntPtr)(&min), (IntPtr)(&max), format, flags: ImGuiSliderFlags.AlwaysClamp))
|
||||
{
|
||||
newValue = value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ImGui.DragScalar(fieldId, dataType, (IntPtr)(&value), dragSpeed, (IntPtr)(&min), (IntPtr)(&max), format))
|
||||
if (ImGui.DragScalar(fieldId, dataType, (IntPtr)(&value), dragSpeed, (IntPtr)(&min), (IntPtr)(&max), format, flags: ImGuiSliderFlags.AlwaysClamp))
|
||||
{
|
||||
newValue = value;
|
||||
}
|
||||
@@ -327,9 +329,6 @@ internal class EntityEditor
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
// TODO: Allow escaped unicode chars \u XXXX
|
||||
// TODO: Add text input validation, so that it isn't possible to type invalid chars
|
||||
|
||||
char* value = stackalloc char[4];
|
||||
switch((char)reference!)
|
||||
{
|
||||
@@ -374,20 +373,143 @@ internal class EntityEditor
|
||||
// Place string delimiter after encoded UTF8 sequence.
|
||||
buffer[encodedBytes] = (byte)'\0';
|
||||
|
||||
if (ImGui.InputText(fieldId, (IntPtr)buffer, 8))
|
||||
// Makes sure the data of a char text field is always valid or can be extended to be a valid char.
|
||||
int CharTextFieldFilter(ImGuiInputTextCallbackData* data)
|
||||
{
|
||||
if (buffer[0] == '\\' && buffer[1] != '\0')
|
||||
try
|
||||
{
|
||||
newValue = (char)buffer[1] switch
|
||||
Span<byte> span = new Span<byte>(data->Buf, data->BufTextLen);
|
||||
|
||||
if (span.IsEmpty)
|
||||
return 0;
|
||||
|
||||
if (span[0] == '\\')
|
||||
{
|
||||
'a' => newValue = '\a',
|
||||
'b' => newValue = '\b',
|
||||
'f' => newValue = '\f',
|
||||
'n' => newValue = '\n',
|
||||
'r' => newValue = '\r',
|
||||
't' => newValue = '\t',
|
||||
'v' => newValue = '\v',
|
||||
_ => newValue = '\\'
|
||||
// It's just the backslash, so we don't need to do anything yet.
|
||||
if (span.Length == 1)
|
||||
return 0;
|
||||
|
||||
if (span[1] == 'a' || span[1] == 'b' || span[1] == 'f' || span[1] == 'n' ||
|
||||
span[1] == 'r' || span[1] == 't' || span[1] == 'v' || span[1] == '\0')
|
||||
{
|
||||
if (span.Length == 2)
|
||||
return 0;
|
||||
|
||||
// Handle simply ascii escape sequence
|
||||
if (span[2] != '\0')
|
||||
{
|
||||
span[2] = 0;
|
||||
data->BufTextLen = 2;
|
||||
data->BufDirty = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if (span[1] == 'u')
|
||||
{
|
||||
// Handle unicode escape sequence \uXXXX
|
||||
// Check if the next four characters are valid hex digits
|
||||
for (int i = 2; i < span.Length; i++)
|
||||
{
|
||||
char c = (char)span[i];
|
||||
|
||||
if (c == '\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Uri.IsHexDigit(c))
|
||||
{
|
||||
span[i] = 0;
|
||||
data->BufTextLen = i;
|
||||
data->BufDirty = 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (span.Length > 6)
|
||||
{
|
||||
span[6] = 0;
|
||||
data->BufTextLen = 6;
|
||||
data->BufDirty = 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (span[1] != '\0')
|
||||
{
|
||||
span[1] = 0;
|
||||
data->BufTextLen = 1;
|
||||
data->BufDirty = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var status = Rune.DecodeFromUtf8(span, out Rune rune, out int bytesConsumed);
|
||||
|
||||
// invalid UTF-8 oder Unicode-symbol needs two UTF-16 chars.
|
||||
if (status != OperationStatus.Done || !rune.IsBmp)
|
||||
{
|
||||
span[0] = 0;
|
||||
data->BufTextLen = 0;
|
||||
data->BufDirty = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: if we want to support Rune as a type, we "just" need to disable this if-check
|
||||
// Cut off everything after the first C# char (which is the first UTF-16 char of the Rune)
|
||||
if (bytesConsumed < span.Length)
|
||||
{
|
||||
span[bytesConsumed] = 0;
|
||||
data->BufTextLen = bytesConsumed;
|
||||
data->BufDirty = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Failed to process char field {fieldName}: {e.Message}.");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make space for the hex code of the char after the textbox
|
||||
ImGui.SetNextItemWidth(ImGui.CalcItemWidth() / 2);
|
||||
|
||||
if (ImGui.InputText(fieldId, (IntPtr)buffer, 8, ImGuiInputTextFlags.CallbackEdit, CharTextFieldFilter))
|
||||
{
|
||||
var dataSpan = new ReadOnlySpan<byte>(buffer, 8);
|
||||
|
||||
if (dataSpan[0] == '\\' && dataSpan[1] == 'u')
|
||||
{
|
||||
// Decode unicode escape sequence \uXXXX
|
||||
string hex = Encoding.UTF8.GetString(dataSpan[2..]);
|
||||
if (int.TryParse(hex, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int codePoint))
|
||||
{
|
||||
newValue = (char)codePoint;
|
||||
}
|
||||
}
|
||||
else if (dataSpan[0] == '\\' && dataSpan[1] != '\0')
|
||||
{
|
||||
newValue = (char)dataSpan[1] switch
|
||||
{
|
||||
'a' => '\a',
|
||||
'b' => '\b',
|
||||
'f' => '\f',
|
||||
'n' => '\n',
|
||||
'r' => '\r',
|
||||
't' => '\t',
|
||||
'v' => '\v',
|
||||
_ => '\\'
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -397,7 +519,11 @@ internal class EntityEditor
|
||||
|
||||
newValue = value[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show the char as hex code
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"(U+{(ushort)(char)reference:X4})");
|
||||
}
|
||||
}
|
||||
else if (fieldType == typeof(byte))
|
||||
|
||||
+15
-23
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace GlitchyEngine;
|
||||
|
||||
@@ -11,66 +12,57 @@ public static class Input
|
||||
/// Returns <see langword="true"/> if the specified key is in the pressed down state.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to query.</param>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern bool IsKeyPressed(Key key);
|
||||
|
||||
public static bool IsKeyPressed(Key key) => ScriptGlue.Input_IsKeyPressed(key);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the specified <see cref="key"/> is in the released state.
|
||||
/// </summary>
|
||||
/// <param name="key">The key to query.</param>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern bool IsKeyReleased(Key key);
|
||||
|
||||
public static bool IsKeyReleased(Key key) => ScriptGlue.Input_IsKeyReleased(key);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the state of the specified <see cref="key"/> changed this frame (i.e. changed from pressed to released or from released to pressed).
|
||||
/// </summary>
|
||||
/// <param name="key">The key to query.</param>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern bool IsKeyToggled(Key key);
|
||||
public static bool IsKeyToggled(Key key) => ScriptGlue.Input_IsKeyToggled(key);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if <see cref="key"/> is being pressed down this frame
|
||||
/// (<see cref="IsKeyPressed"/> was <see langword="false"/> last frame and is <see langword="true"/> this frame).
|
||||
/// </summary>
|
||||
/// <param name="key">The key to query.</param>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern bool IsKeyPressing(Key key);
|
||||
|
||||
public static bool IsKeyPressing(Key key) => ScriptGlue.Input_IsKeyPressing(key);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if <see cref="key"/> is being released this frame
|
||||
/// (<see cref="IsKeyPressed"/> was <see langword="true"/> last frame and is <see langword="false"/> this frame).
|
||||
/// </summary>
|
||||
/// <param name="key">The key to query.</param>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern bool IsKeyReleasing(Key key);
|
||||
|
||||
public static bool IsKeyReleasing(Key key) => ScriptGlue.Input_IsKeyReleasing(key);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the specified mouse button is in the pressed down state.
|
||||
/// </summary>
|
||||
/// <param name="mouseButton">The button to query.</param>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern bool IsMouseButtonPressed(MouseButton mouseButton);
|
||||
public static bool IsMouseButtonPressed(MouseButton mouseButton) => ScriptGlue.Input_IsMouseButtonPressed(mouseButton);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the specified mouse button is in the released state.
|
||||
/// </summary>
|
||||
/// <param name="mouseButton">The button to query.</param>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern bool IsMouseButtonReleased(MouseButton mouseButton);
|
||||
|
||||
public static bool IsMouseButtonReleased(MouseButton mouseButton) => ScriptGlue.Input_IsMouseButtonReleased(mouseButton);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the mouse button is being pressed down this frame
|
||||
/// (<see cref="IsMouseButtonPressed"/> was <see langword="false"/> last frame and is <see langword="true"/> this frame).
|
||||
/// </summary>
|
||||
/// <param name="mouseButton">The button to query.</param>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern bool IsMouseButtonPressing(MouseButton mouseButton);
|
||||
public static bool IsMouseButtonPressing(MouseButton mouseButton) => ScriptGlue.Input_IsMouseButtonPressing(mouseButton);
|
||||
|
||||
/// <summary>
|
||||
/// Returns <see langword="true"/> if the mouse button is being released this frame
|
||||
/// (<see cref="IsMouseButtonPressed"/> was <see langword="true"/> last frame and is <see langword="false"/> this frame).
|
||||
/// </summary>
|
||||
/// <param name="mouseButton">The button to query.</param>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern bool IsMouseButtonReleasing(MouseButton mouseButton);
|
||||
public static bool IsMouseButtonReleasing(MouseButton mouseButton) => ScriptGlue.Input_IsMouseButtonReleasing(mouseButton);
|
||||
}
|
||||
|
||||
+86
-22
@@ -1,7 +1,11 @@
|
||||
using GlitchyEngine.Core;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GlitchyEngine.Core;
|
||||
using System.Runtime.InteropServices;
|
||||
using static GlitchyEngine.Log;
|
||||
|
||||
namespace GlitchyEngine;
|
||||
|
||||
@@ -31,9 +35,10 @@ public class Log
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Trace(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Trace(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Trace, message, callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Trace, ScriptGlue.CurrentEntityId, message, callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -42,9 +47,10 @@ public class Log
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Info(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Info(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Info, message, callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Info, ScriptGlue.CurrentEntityId, message, callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -53,9 +59,10 @@ public class Log
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Warning(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Warning(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Warning, message, callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Warning, ScriptGlue.CurrentEntityId, message, callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,9 +71,10 @@ public class Log
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Error(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Error(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Error, message, callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Error, ScriptGlue.CurrentEntityId, message, callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -75,9 +83,10 @@ public class Log
|
||||
/// <param name="message">The message to log.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Critical(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Critical(string message, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Critical, message, callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Critical, ScriptGlue.CurrentEntityId, message, callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,9 +95,10 @@ public class Log
|
||||
/// <param name="obj">The object to serialize.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Trace(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Trace(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Trace, obj.ToString(), callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Trace, ScriptGlue.CurrentEntityId, obj.ToString(), callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -97,9 +107,10 @@ public class Log
|
||||
/// <param name="obj">The object to serialize.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Info(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Info(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Info, obj.ToString(), callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Info, ScriptGlue.CurrentEntityId, obj.ToString(), callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -108,9 +119,10 @@ public class Log
|
||||
/// <param name="obj">The object to serialize.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Warning(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Warning(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Warning, obj.ToString(), callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Warning, ScriptGlue.CurrentEntityId, obj.ToString(), callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,9 +131,10 @@ public class Log
|
||||
/// <param name="obj">The object to serialize.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Error(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Error(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Error, obj.ToString(), callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Error, ScriptGlue.CurrentEntityId, obj.ToString(), callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -130,9 +143,30 @@ public class Log
|
||||
/// <param name="obj">The object to serialize.</param>
|
||||
/// <param name="callerFilePath">File path of the caller.</param>
|
||||
/// <param name="callerLineNumber">Line number of the caller.</param>
|
||||
public static void Critical(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0)
|
||||
/// <param name="callerMemberName">Name of the calling member.</param>
|
||||
public static void Critical(object obj, [CallerFilePath]string callerFilePath = "", [CallerLineNumber]int callerLineNumber = 0, [CallerMemberName]string callerMemberName = "")
|
||||
{
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Critical, obj.ToString(), callerFilePath, callerLineNumber);
|
||||
ScriptGlue.Log_LogMessage(LogLevel.Critical, ScriptGlue.CurrentEntityId, obj.ToString(), callerFilePath, callerMemberName, callerLineNumber, 0);
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{ToString(),raw}")]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0)]
|
||||
[EngineClass("GlitchyEngine.Scripting.ScriptGlue.GlueStackFrameInfo")]
|
||||
internal struct StackFrameInfo
|
||||
{
|
||||
public Native.StringView FileName;
|
||||
public Native.StringView MethodSignature;
|
||||
public int LineNumber;
|
||||
public int ColumnNumber;
|
||||
}
|
||||
|
||||
static string GetFullSignature(MethodBase method)
|
||||
{
|
||||
var typeName = method.DeclaringType?.FullName ?? "<unknown>";
|
||||
var parameters = string.Join(", ",
|
||||
method.GetParameters().Select(p => $"{p.ParameterType.Name}"));
|
||||
|
||||
return $"{typeName}.{method.Name}({parameters})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -141,6 +175,36 @@ public class Log
|
||||
/// <param name="exception">The exception to log.</param>
|
||||
public static void Exception(Exception exception)
|
||||
{
|
||||
ScriptGlue.Log_LogException(UUID.Zero, exception.GetType().FullName, exception.Message, exception.StackTrace);
|
||||
StackTrace st = new StackTrace(exception, true);
|
||||
StackFrame[] frames = st.GetFrames();
|
||||
|
||||
StackFrameInfo[] stackFrameInfos = new StackFrameInfo[frames.Length];
|
||||
|
||||
// We only need StackFrameInfo for exception logging, so it's okay to manually handle the marshalling here.
|
||||
foreach (StackFrame stackFrame in frames)
|
||||
{
|
||||
MethodBase? method = stackFrame.GetMethod();
|
||||
string methodSignature = method != null ? GetFullSignature(method) : "<unknown>";
|
||||
stackFrameInfos[frames.IndexOf(stackFrame)] = new StackFrameInfo
|
||||
{
|
||||
FileName = Native.StringView.FromManagedString(stackFrame.GetFileName()),
|
||||
MethodSignature = Native.StringView.FromManagedString(methodSignature),
|
||||
LineNumber = stackFrame.GetFileLineNumber(),
|
||||
ColumnNumber = stackFrame.GetFileColumnNumber()
|
||||
};
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (StackFrameInfo* stackFrames = stackFrameInfos)
|
||||
{
|
||||
ScriptGlue.Log_LogException(ScriptGlue.CurrentEntityId, exception.GetType().FullName, exception.Message, stackFrames, stackFrameInfos.Length);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (StackFrameInfo stackFrameInfo in stackFrameInfos)
|
||||
{
|
||||
Native.StringView.FreeNativeMemory(stackFrameInfo.FileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<BaseOutputPath></BaseOutputPath>
|
||||
<RootNamespace>GlitchyEngine</RootNamespace>
|
||||
|
||||
+134
-8
@@ -8,12 +8,15 @@ using ImGuiNET;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace GlitchyEngine;
|
||||
|
||||
@@ -111,25 +114,53 @@ internal static unsafe partial class ScriptGlue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counter for the number of script contexts that have been created. This allows us to debug and track the number of script contexts that have been created and destroyed.
|
||||
/// </summary>
|
||||
private static int _contextCounter = 0;
|
||||
|
||||
private static void LoadAssembly(Stream assemblyStream, Stream? pdbStream)
|
||||
{
|
||||
_scriptAssemblyContext ??= new AssemblyLoadContext("ScriptContext", true);
|
||||
Debug.Assert(_scriptAssemblyContext == null);
|
||||
Debug.Assert(_appAssembly == null);
|
||||
|
||||
// A new AssemblyLoadContext for the script assembly allows us to unload the assembly later.
|
||||
_scriptAssemblyContext = new AssemblyLoadContext($"ScriptContext_{Interlocked.Increment(ref _contextCounter)}", true);
|
||||
|
||||
_appAssembly = _scriptAssemblyContext.LoadFromStream(assemblyStream, pdbStream);
|
||||
|
||||
Debug.Assert(_appAssembly != null);
|
||||
}
|
||||
|
||||
|
||||
private static WeakReference? _weakAssemblyRef;
|
||||
private static string? _lastAssemblyName;
|
||||
|
||||
/// <summary>
|
||||
/// Called by the engine to unload the assembly (containing user scripts).
|
||||
/// Called by the engine to unload the assembly containing user scripts.
|
||||
/// </summary>
|
||||
[UnmanagedCallersOnly]
|
||||
public static void UnloadAssemblies()
|
||||
public static void UnloadScriptAssembly()
|
||||
{
|
||||
try
|
||||
{
|
||||
_scriptAssemblyContext?.Unload();
|
||||
Debug.Assert(EntityScriptInstances.Count == 0, "Can not unload assemblies while entities are still active!");
|
||||
|
||||
// TODO: Should we allow calling unload when we never loaded?
|
||||
if (_scriptAssemblyContext == null)
|
||||
return;
|
||||
|
||||
_weakAssemblyRef = new WeakReference(_scriptAssemblyContext);
|
||||
_lastAssemblyName = _scriptAssemblyContext.Name;
|
||||
Debug.WriteLine($"--- Killing {_lastAssemblyName}...");
|
||||
|
||||
// TODO: we should probably have a way to collect and terminate threads. (like a EngineThread override or something)
|
||||
|
||||
_scriptAssemblyContext.Unload();
|
||||
_scriptAssemblyContext = null;
|
||||
_appAssembly = null;
|
||||
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -137,6 +168,42 @@ internal static unsafe partial class ScriptGlue
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the old assembly is dead.
|
||||
/// </summary>
|
||||
[UnmanagedCallersOnly]
|
||||
public static void WaitUntilOldAssemblyDead()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_weakAssemblyRef == null)
|
||||
return;
|
||||
|
||||
int iterations = 0;
|
||||
|
||||
while (_weakAssemblyRef.IsAlive)
|
||||
{
|
||||
GC.Collect();
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
// We must sleep a bit, otherwise we absolutely wreck the debugger.
|
||||
Thread.Sleep(10);
|
||||
iterations++;
|
||||
}
|
||||
Debug.WriteLine($"--- {_lastAssemblyName} is now dead after {iterations} garbage collections!");
|
||||
|
||||
_weakAssemblyRef = null;
|
||||
_lastAssemblyName = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the old assembly is dead.
|
||||
/// </summary>
|
||||
private static NativeScriptClassInfo[]? _unsafeClasses;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0)]
|
||||
@@ -272,11 +339,16 @@ internal static unsafe partial class ScriptGlue
|
||||
_unsafeClasses = null;
|
||||
}
|
||||
|
||||
private static readonly AsyncLocal<UUID> _currentEntityId = new ();
|
||||
|
||||
internal static UUID CurrentEntityId => _currentEntityId.Value;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void ShowEntityEditor(UUID entityId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentEntityId.Value = entityId;
|
||||
(Entity entity, Type type) = EntityScriptInstances[entityId];
|
||||
EntityEditor.ShowEntityEditor(entity);
|
||||
}
|
||||
@@ -284,6 +356,10 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentEntityId.Value = UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
@@ -291,6 +367,7 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentEntityId.Value = entityId;
|
||||
(Entity entity, Type type) = EntityScriptInstances[entityId];
|
||||
entity.OnCreate();
|
||||
}
|
||||
@@ -298,6 +375,10 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentEntityId.Value = UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
@@ -305,6 +386,7 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentEntityId.Value = entityId;
|
||||
(Entity entity, Type type) = EntityScriptInstances[entityId];
|
||||
entity.OnUpdate(deltaTime);
|
||||
}
|
||||
@@ -312,6 +394,10 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentEntityId.Value = UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
@@ -319,6 +405,7 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentEntityId.Value = entityId;
|
||||
(Entity entity, Type type) = EntityScriptInstances[entityId];
|
||||
entity.OnCollisionEnter2D(collision);
|
||||
}
|
||||
@@ -326,6 +413,10 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentEntityId.Value = UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
@@ -333,6 +424,7 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentEntityId.Value = entityId;
|
||||
if (EntityScriptInstances.Remove(entityId, out var match) && callDestroy > 0)
|
||||
{
|
||||
match.Entity.OnDestroy();
|
||||
@@ -342,6 +434,10 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentEntityId.Value = UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Type? GetTypeFromNativeString(byte* fullTypeName)
|
||||
@@ -365,6 +461,7 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentEntityId.Value = entityId;
|
||||
Type? scriptType = GetTypeFromNativeString(scriptClassName);
|
||||
|
||||
Debug.Assert(scriptType != null, "Script class Type not found.");
|
||||
@@ -379,6 +476,10 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentEntityId.Value = UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
struct ComponentFunctionPointers
|
||||
@@ -459,6 +560,7 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentEntityId.Value = entityId;
|
||||
if (!EntityScriptInstances.TryGetValue(entityId, out var match))
|
||||
return;
|
||||
|
||||
@@ -468,6 +570,10 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentEntityId.Value = UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
@@ -475,6 +581,7 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentEntityId.Value = entityId;
|
||||
if (!EntityScriptInstances.TryGetValue(entityId, out var match))
|
||||
return;
|
||||
|
||||
@@ -484,6 +591,10 @@ internal static unsafe partial class ScriptGlue
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentEntityId.Value = UUID.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
@@ -518,6 +629,19 @@ internal static unsafe partial class ScriptGlue
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void EntitySerializer_FinishSerialization(IntPtr engineSerializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
EntitySerializer.FinishSerialization(engineSerializer);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion EntitySerializer
|
||||
|
||||
#endregion
|
||||
@@ -544,13 +668,15 @@ internal static unsafe partial class ScriptGlue
|
||||
public static void Entity_GetScriptInstance(UUID entityId, out Entity? instance)
|
||||
{
|
||||
// We currently can implement this method here, because we only have C# scripts.
|
||||
// If we ever need to do something to interop with other script languages, then this would change.
|
||||
// If we ever need to do something to interop with other script languages, then this would probably need to communicate with the engine.
|
||||
if (EntityScriptInstances.TryGetValue(entityId, out var match))
|
||||
{
|
||||
instance = match.Entity;
|
||||
}
|
||||
|
||||
instance = null;
|
||||
else
|
||||
{
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Serialization_SerializeField(IntPtr serializationContext, SerializationType type, string fieldName, object? valueObject, string? fullTypeName)
|
||||
|
||||
@@ -33,10 +33,21 @@ internal static class EntitySerializer
|
||||
|
||||
internal static void DestroySerializationContext(IntPtr engineSerializer)
|
||||
{
|
||||
SerializationObjects.Remove(engineSerializer);
|
||||
DeserializationObjects.Remove(engineSerializer);
|
||||
if (SerializationObjects.Remove(engineSerializer, out var serializedObjects))
|
||||
{
|
||||
serializedObjects.Clear();
|
||||
}
|
||||
|
||||
if (DeserializationObjects.Remove(engineSerializer, out var deserializationObjects))
|
||||
{
|
||||
deserializationObjects.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void FinishSerialization(IntPtr engineSerializer)
|
||||
{
|
||||
ClearSerializationContext(engineSerializer);
|
||||
}
|
||||
|
||||
internal static void Serialize(Entity entity, IntPtr engineObject, IntPtr engineSerializer)
|
||||
{
|
||||
SerializedObject obj = new SerializedObject(engineObject, false, UUID.Zero, SerializationObjects[engineSerializer]);
|
||||
|
||||
@@ -107,6 +107,7 @@ public class SerializedObject
|
||||
|
||||
public void Serialize(Entity entity)
|
||||
{
|
||||
_serializedClasses.Add(entity, this);
|
||||
SerializeInstanceFields(entity);
|
||||
}
|
||||
|
||||
@@ -321,7 +322,7 @@ public class SerializedObject
|
||||
{
|
||||
if (fieldType.IsSubclassOf(typeof(EngineObject)))
|
||||
{
|
||||
AddValueTypeField(fieldName, SerializationType.ObjectReference, ((EngineObject?)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName);
|
||||
AddValueTypeField(fieldName, SerializationType.EngineObjectReference, ((EngineObject?)fieldValue)?.UUID ?? UUID.Zero, fieldValue?.GetType().FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -122,7 +122,8 @@ public class ScriptGlueGenerator : IIncrementalGenerator
|
||||
None,
|
||||
Ref,
|
||||
In,
|
||||
Out
|
||||
Out,
|
||||
Pointer
|
||||
}
|
||||
|
||||
private static (MappedType Type, TypeModifier Modifier) DecodeType(string beefType, Dictionary<string, MappedType> beefTypeToMappedType)
|
||||
@@ -145,6 +146,15 @@ public class ScriptGlueGenerator : IIncrementalGenerator
|
||||
beefType = beefType.Substring(3);
|
||||
}
|
||||
|
||||
if (beefTypeToMappedType.TryGetValue(beefType, out var mapping))
|
||||
return (mapping, modifier);
|
||||
|
||||
if (beefType.EndsWith("*"))
|
||||
{
|
||||
modifier = TypeModifier.Pointer;
|
||||
beefType = beefType.Substring(0, beefType.Length - 1);
|
||||
}
|
||||
|
||||
if (beefTypeToMappedType.TryGetValue(beefType, out MappedType? mappedType))
|
||||
{
|
||||
return (mappedType, modifier);
|
||||
@@ -296,6 +306,14 @@ public class ScriptGlueGenerator : IIncrementalGenerator
|
||||
}
|
||||
|
||||
parameterText.Append(parameterType.CSharpTypeName);
|
||||
|
||||
switch (parameterModifier)
|
||||
{
|
||||
case TypeModifier.Pointer:
|
||||
parameterText.Append("*");
|
||||
break;
|
||||
}
|
||||
|
||||
parameterText.Append(", ");
|
||||
}
|
||||
|
||||
@@ -345,7 +363,12 @@ public class ScriptGlueGenerator : IIncrementalGenerator
|
||||
break;
|
||||
}
|
||||
|
||||
parameterText.Append($"{parameterType.CSharpWrapperType} {param.Name}");
|
||||
parameterText.Append($"{parameterType.CSharpWrapperType}");
|
||||
|
||||
if (parameterModifier == TypeModifier.Pointer)
|
||||
parameterText.Append("*");
|
||||
|
||||
parameterText.Append($" {param.Name}");
|
||||
|
||||
switch (parameterModifier)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user