replace nonfunctional managed asserts with proper imgui-handled assert mechanism

This commit is contained in:
goat 2024-12-25 12:46:54 +01:00
parent 8773d3b873
commit 12bf2f4478
12 changed files with 238 additions and 207 deletions

View file

@ -243,7 +243,7 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
/// <summary>
/// Gets or sets a value indicating whether or not ImGui asserts should be enabled at startup.
/// </summary>
public bool AssertsEnabledAtStartup { get; set; }
public bool? ImGuiAssertsEnabledAtStartup { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not docking should be globally enabled in ImGui.
@ -605,6 +605,12 @@ internal sealed class DalamudConfiguration : IInternalDisposableService
this.AutoUpdateBehavior ??= this.AutoUpdatePlugins
? Plugin.Internal.AutoUpdate.AutoUpdateBehavior.UpdateAll
: Plugin.Internal.AutoUpdate.AutoUpdateBehavior.OnlyNotify;
// Turn ImGui asserts on by default if we have any active dev plugins
if (!this.ImGuiAssertsEnabledAtStartup.HasValue && this.DevPluginLoadLocations.Any(x => x.IsEnabled))
{
this.ImGuiAssertsEnabledAtStartup = true;
}
#pragma warning restore CS0618
}

View file

@ -0,0 +1,177 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Dalamud.Utility;
using Serilog;
namespace Dalamud.Interface.Internal;
/// <summary>
/// Class responsible for registering and handling ImGui asserts.
/// </summary>
internal class AssertHandler : IDisposable
{
private readonly HashSet<string> ignoredAsserts = [];
// Store callback to avoid it from being GC'd
private readonly AssertCallbackDelegate callback;
/// <summary>
/// Initializes a new instance of the <see cref="AssertHandler"/> class.
/// </summary>
public AssertHandler()
{
this.callback = (expr, file, line) => this.OnImGuiAssert(expr, file, line);
}
private delegate void AssertCallbackDelegate(
[MarshalAs(UnmanagedType.LPStr)] string expr,
[MarshalAs(UnmanagedType.LPStr)] string file,
int line);
/// <summary>
/// Gets or sets a value indicating whether ImGui asserts should be shown to the user.
/// </summary>
public bool ShowAsserts { get; set; } = false;
/// <summary>
/// Register the cimgui assert handler with the native library.
/// </summary>
public void Setup()
{
CustomNativeFunctions.igCustom_SetAssertCallback(this.callback);
}
/// <summary>
/// Unregister the cimgui assert handler with the native library.
/// </summary>
public void Shutdown()
{
CustomNativeFunctions.igCustom_SetAssertCallback(null);
}
/// <inheritdoc/>
public void Dispose()
{
this.Shutdown();
}
private void OnImGuiAssert(string expr, string file, int line)
{
Log.Warning("ImGui assertion failed: {Expr} at {File}:{Line}", expr, file, line);
if (!this.ShowAsserts)
return;
var key = $"{file}:{line}";
if (this.ignoredAsserts.Contains(key))
return;
// TODO: It would be nice to get unmanaged stack frames here, seems hard though without pulling that
// entire code in from the crash handler
var originalStackTrace = new StackTrace(2).ToString();
string? GetRepoUrl()
{
// TODO: implot, imguizmo?
const string userName = "goatcorp";
const string repoName = "gc-imgui";
const string branch = "1.88-enhanced-abifix";
if (!file.Contains("imgui", StringComparison.OrdinalIgnoreCase))
return null;
var lastSlash = file.LastIndexOf('\\');
var fileName = file[(lastSlash + 1)..];
return $"https://github.com/{userName}/{repoName}/blob/{branch}/{fileName}#L{line}";
}
var gitHubUrl = GetRepoUrl();
var showOnGitHubButton = new TaskDialogButton
{
Text = "Show on GitHub",
AllowCloseDialog = false,
Enabled = !gitHubUrl.IsNullOrEmpty(),
};
showOnGitHubButton.Click += (_, _) =>
{
if (!gitHubUrl.IsNullOrEmpty())
Util.OpenLink(gitHubUrl);
};
var breakButton = new TaskDialogButton
{
Text = "Break",
AllowCloseDialog = true,
};
var ignoreButton = TaskDialogButton.Ignore;
var abortButton = TaskDialogButton.Abort;
TaskDialogButton? result = null;
void DialogThreadStart()
{
// TODO(goat): This is probably not gonna work if we showed the loading dialog
// this session since it already loaded visual styles...
Application.EnableVisualStyles();
var page = new TaskDialogPage()
{
Heading = "ImGui assertion failed",
Caption = "Dalamud",
Expander = new TaskDialogExpander
{
CollapsedButtonText = "Show stack trace",
ExpandedButtonText = "Hide stack trace",
Text = originalStackTrace,
},
Text = $"Some code in a plugin or Dalamud itself has caused an internal assertion in ImGui to fail. The game will most likely crash now.\n\n{expr}\nAt: {file}:{line}",
Icon = TaskDialogIcon.Warning,
Buttons =
[
showOnGitHubButton,
breakButton,
ignoreButton,
abortButton,
],
DefaultButton = showOnGitHubButton,
};
result = TaskDialog.ShowDialog(page);
}
// Run in a separate thread because of STA and to not mess up other stuff
var thread = new Thread(DialogThreadStart)
{
Name = "Dalamud ImGui Assert Dialog",
};
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
if (result == breakButton)
{
Debugger.Break();
}
else if (result == abortButton)
{
Environment.Exit(-1);
}
else if (result == ignoreButton)
{
this.ignoredAsserts.Add(key);
}
}
private static class CustomNativeFunctions
{
[DllImport("cimgui", CallingConvention = CallingConvention.Cdecl)]
#pragma warning disable SA1300
public static extern void igCustom_SetAssertCallback(AssertCallbackDelegate callback);
#pragma warning restore SA1300
}
}

View file

@ -16,7 +16,6 @@ using Dalamud.Game.Text;
using Dalamud.Hooking.WndProcHook;
using Dalamud.Interface.Colors;
using Dalamud.Interface.GameFonts;
using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Interface.Utility;
@ -185,7 +184,7 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
return true;
if (!ImGui.GetIO().ConfigInputTextCursorBlink)
return true;
var textState = TextState;
var textState = CustomNativeFunctions.igCustom_GetInputTextState();
if (textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0)
return true;
if (textState->CursorAnim <= 0)
@ -194,9 +193,6 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
}
}
private static ImGuiInputTextState* TextState =>
(ImGuiInputTextState*)(ImGui.GetCurrentContext() + ImGuiContextOffsets.TextStateOffset);
/// <summary>Gets a value indicating whether to display partial conversion status.</summary>
private bool ShowPartialConversion => this.partialConversionFrom != 0 ||
this.partialConversionTo != this.compositionString.Length;
@ -341,7 +337,8 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
try
{
var invalidTarget = TextState->Id == 0 || (TextState->Flags & ImGuiInputTextFlags.ReadOnly) != 0;
var textState = CustomNativeFunctions.igCustom_GetInputTextState();
var invalidTarget = textState->Id == 0 || (textState->Flags & ImGuiInputTextFlags.ReadOnly) != 0;
#if IMEDEBUG
switch (args.Message)
@ -570,19 +567,20 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
this.ReflectCharacterEncounters(newString);
var textState = CustomNativeFunctions.igCustom_GetInputTextState();
if (this.temporaryUndoSelection is not null)
{
TextState->Undo();
TextState->SelectionTuple = this.temporaryUndoSelection.Value;
textState->Undo();
textState->SelectionTuple = this.temporaryUndoSelection.Value;
this.temporaryUndoSelection = null;
}
TextState->SanitizeSelectionRange();
if (TextState->ReplaceSelectionAndPushUndo(newString))
this.temporaryUndoSelection = TextState->SelectionTuple;
textState->SanitizeSelectionRange();
if (textState->ReplaceSelectionAndPushUndo(newString))
this.temporaryUndoSelection = textState->SelectionTuple;
// Put the cursor at the beginning, so that the candidate window appears aligned with the text.
TextState->SetSelectionRange(TextState->SelectionTuple.Start, newString.Length, 0);
textState->SetSelectionRange(textState->SelectionTuple.Start, newString.Length, 0);
if (finalCommit)
{
@ -627,7 +625,10 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
this.partialConversionFrom = this.partialConversionTo = 0;
this.compositionCursorOffset = 0;
this.temporaryUndoSelection = null;
TextState->Stb.SelectStart = TextState->Stb.Cursor = TextState->Stb.SelectEnd;
var textState = CustomNativeFunctions.igCustom_GetInputTextState();
textState->Stb.SelectStart = textState->Stb.Cursor = textState->Stb.SelectEnd;
this.candidateStrings.Clear();
this.immCandNative = default;
if (invokeCancel)
@ -1113,6 +1114,14 @@ internal sealed unsafe class DalamudIme : IInternalDisposableService
}
}
private static class CustomNativeFunctions
{
[DllImport("cimgui")]
#pragma warning disable SA1300
public static extern ImGuiInputTextState* igCustom_GetInputTextState();
#pragma warning restore SA1300
}
#if IMEDEBUG
private static class ImeDebug
{

View file

@ -18,7 +18,6 @@ using Dalamud.Game.Internal;
using Dalamud.Hooking;
using Dalamud.Interface.Animation.EasingFunctions;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.Windows;
using Dalamud.Interface.Internal.Windows.Data;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
@ -163,7 +162,7 @@ internal class DalamudInterface : IInternalDisposableService
this.WindowSystem.AddWindow(this.branchSwitcherWindow);
this.WindowSystem.AddWindow(this.hitchSettingsWindow);
ImGuiManagedAsserts.AssertsEnabled = configuration.AssertsEnabledAtStartup;
this.interfaceManager.ShowAsserts = configuration.ImGuiAssertsEnabledAtStartup ?? false;
this.isImGuiDrawDevMenu = this.isImGuiDrawDevMenu || configuration.DevBarOpenAtStartup;
this.interfaceManager.Draw += this.OnDraw;
@ -833,6 +832,12 @@ internal class DalamudInterface : IInternalDisposableService
}
}
if (ImGui.MenuItem("Cause ImGui assert"))
{
ImGui.PopStyleVar();
ImGui.PopStyleVar();
}
ImGui.EndMenu();
}
@ -865,15 +870,16 @@ internal class DalamudInterface : IInternalDisposableService
ImGui.Separator();
var val = ImGuiManagedAsserts.AssertsEnabled;
var val = this.interfaceManager.ShowAsserts;
if (ImGui.MenuItem("Enable Asserts", string.Empty, ref val))
{
ImGuiManagedAsserts.AssertsEnabled = val;
this.interfaceManager.ShowAsserts = val;
}
if (ImGui.MenuItem("Enable asserts at startup", null, this.configuration.AssertsEnabledAtStartup))
var assertsEnabled = this.configuration.ImGuiAssertsEnabledAtStartup ?? false;
if (ImGui.MenuItem("Enable asserts at startup", null, assertsEnabled))
{
this.configuration.AssertsEnabledAtStartup = !this.configuration.AssertsEnabledAtStartup;
this.configuration.ImGuiAssertsEnabledAtStartup = !assertsEnabled;
this.configuration.QueueSave();
}

View file

@ -20,7 +20,6 @@ using Dalamud.Hooking.WndProcHook;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Interface.ImGuiNotification.Internal;
using Dalamud.Interface.Internal.DesignSystem;
using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.Internal.ReShadeHandling;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.ManagedFontAtlas.Internals;
@ -94,6 +93,8 @@ internal partial class InterfaceManager : IInternalDisposableService
private readonly ConcurrentQueue<Action> runBeforeImGuiRender = new();
private readonly ConcurrentQueue<Action> runAfterImGuiRender = new();
private readonly AssertHandler assertHandler = new();
private RawDX11Scene? scene;
private Hook<SetCursorDelegate>? setCursorHook;
@ -267,11 +268,20 @@ internal partial class InterfaceManager : IInternalDisposableService
/// </remarks>
public long CumulativePresentCalls { get; private set; }
/// <inheritdoc cref="AssertHandler.ShowAsserts"/>
public bool ShowAsserts
{
get => this.assertHandler.ShowAsserts;
set => this.assertHandler.ShowAsserts = value;
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
void IInternalDisposableService.DisposeService()
{
this.assertHandler.Dispose();
// Unload hooks from the framework thread if possible.
// We're currently off the framework thread, as this function can only be called from
// ServiceManager.UnloadAllServices, which is called from EntryPoint.RunThread.
@ -565,6 +575,7 @@ internal partial class InterfaceManager : IInternalDisposableService
{
try
{
this.assertHandler.Setup();
newScene = new RawDX11Scene((nint)swapChain);
}
catch (DllNotFoundException ex)
@ -1128,15 +1139,11 @@ internal partial class InterfaceManager : IInternalDisposableService
WindowSystem.HasAnyWindowSystemFocus = false;
WindowSystem.FocusedWindowSystemNamespace = string.Empty;
var snap = ImGuiManagedAsserts.GetSnapshot();
if (this.IsDispatchingEvents)
{
this.Draw?.Invoke();
Service<NotificationManager>.GetNullable()?.Draw();
}
ImGuiManagedAsserts.ReportProblems("Dalamud Core", snap);
}
/// <summary>

View file

@ -1,23 +0,0 @@
namespace Dalamud.Interface.Internal.ManagedAsserts;
/// <summary>
/// Offsets to various data in ImGui context.
/// </summary>
/// <remarks>
/// Last updated for ImGui 1.83.
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Document the unsage instead.")]
internal static class ImGuiContextOffsets
{
public const int CurrentWindowStackOffset = 0x73A;
public const int ColorStackOffset = 0x79C;
public const int StyleVarStackOffset = 0x7A0;
public const int FontStackOffset = 0x7A4;
public const int BeginPopupStackOffset = 0x7B8;
public const int TextStateOffset = 0x4588;
}

View file

@ -1,140 +0,0 @@
using System.Diagnostics;
using ImGuiNET;
using static Dalamud.NativeFunctions;
namespace Dalamud.Interface.Internal.ManagedAsserts;
/// <summary>
/// Report ImGui problems with a MessageBox dialog.
/// </summary>
internal static class ImGuiManagedAsserts
{
/// <summary>
/// Gets or sets a value indicating whether asserts are enabled for ImGui.
/// </summary>
public static bool AssertsEnabled { get; set; }
/// <summary>
/// Create a snapshot of the current ImGui context.
/// Should be called before rendering an ImGui frame.
/// </summary>
/// <returns>A snapshot of the current context.</returns>
public static unsafe ImGuiContextSnapshot GetSnapshot()
{
var contextPtr = ImGui.GetCurrentContext();
var styleVarStack = *((int*)contextPtr + ImGuiContextOffsets.StyleVarStackOffset); // ImVector.Size
var colorStack = *((int*)contextPtr + ImGuiContextOffsets.ColorStackOffset); // ImVector.Size
var fontStack = *((int*)contextPtr + ImGuiContextOffsets.FontStackOffset); // ImVector.Size
var popupStack = *((int*)contextPtr + ImGuiContextOffsets.BeginPopupStackOffset); // ImVector.Size
var windowStack = *((int*)contextPtr + ImGuiContextOffsets.CurrentWindowStackOffset); // ImVector.Size
return new ImGuiContextSnapshot
{
StyleVarStackSize = styleVarStack,
ColorStackSize = colorStack,
FontStackSize = fontStack,
BeginPopupStackSize = popupStack,
WindowStackSize = windowStack,
};
}
/// <summary>
/// Compare a snapshot to the current post-draw state and report any errors in a MessageBox dialog.
/// </summary>
/// <param name="source">The source of any problems, something to blame.</param>
/// <param name="before">ImGui context snapshot.</param>
public static void ReportProblems(string source, ImGuiContextSnapshot before)
{
// TODO: Needs to be updated for ImGui 1.88
return;
#pragma warning disable CS0162
if (!AssertsEnabled)
{
return;
}
var cSnap = GetSnapshot();
if (before.StyleVarStackSize != cSnap.StyleVarStackSize)
{
ShowAssert(source, $"You forgot to pop a style var!\n\nBefore: {before.StyleVarStackSize}, after: {cSnap.StyleVarStackSize}");
return;
}
if (before.ColorStackSize != cSnap.ColorStackSize)
{
ShowAssert(source, $"You forgot to pop a color!\n\nBefore: {before.ColorStackSize}, after: {cSnap.ColorStackSize}");
return;
}
if (before.FontStackSize != cSnap.FontStackSize)
{
ShowAssert(source, $"You forgot to pop a font!\n\nBefore: {before.FontStackSize}, after: {cSnap.FontStackSize}");
return;
}
if (before.BeginPopupStackSize != cSnap.BeginPopupStackSize)
{
ShowAssert(source, $"You forgot to end a popup!\n\nBefore: {before.BeginPopupStackSize}, after: {cSnap.BeginPopupStackSize}");
return;
}
if (cSnap.WindowStackSize != 1)
{
if (cSnap.WindowStackSize > 1)
{
ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}");
}
else
{
ShowAssert(source, $"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?\n\ncSnap.WindowStackSize = {cSnap.WindowStackSize}");
}
}
#pragma warning restore CS0162
}
private static void ShowAssert(string source, string message)
{
var caption = $"You fucked up";
message = $"{message}\n\nSource: {source}\n\nAsserts are now disabled. You may re-enable them.";
var flags = MessageBoxType.Ok | MessageBoxType.IconError;
_ = MessageBoxW(Process.GetCurrentProcess().MainWindowHandle, message, caption, flags);
AssertsEnabled = false;
}
/// <summary>
/// A snapshot of various ImGui context properties.
/// </summary>
public class ImGuiContextSnapshot
{
/// <summary>
/// Gets the ImGui style var stack size.
/// </summary>
public int StyleVarStackSize { get; init; }
/// <summary>
/// Gets the ImGui color stack size.
/// </summary>
public int ColorStackSize { get; init; }
/// <summary>
/// Gets the ImGui font stack size.
/// </summary>
public int FontStackSize { get; init; }
/// <summary>
/// Gets the ImGui begin popup stack size.
/// </summary>
public int BeginPopupStackSize { get; init; }
/// <summary>
/// Gets the ImGui window stack size.
/// </summary>
public int WindowStackSize { get; init; }
}
}

View file

@ -222,6 +222,10 @@ public class DevPluginsSettingsEntry : SettingsEntry
this.devPluginLocationsChanged = true;
this.devPluginTempLocation = string.Empty;
}
var config = Service<DalamudConfiguration>.Get();
if (!config.ImGuiAssertsEnabledAtStartup.HasValue)
config.ImGuiAssertsEnabledAtStartup = true;
}
public override void PostDraw()

View file

@ -9,7 +9,6 @@ using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Gui;
using Dalamud.Interface.FontIdentifier;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.ManagedAsserts;
using Dalamud.Interface.ManagedFontAtlas;
using Dalamud.Interface.ManagedFontAtlas.Internals;
using Dalamud.Plugin.Internal.Types;
@ -713,8 +712,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
ImGui.End();
}
var snapshot = this.Draw is null ? null : ImGuiManagedAsserts.GetSnapshot();
try
{
this.Draw?.InvokeSafely();
@ -728,10 +725,6 @@ public sealed class UiBuilder : IDisposable, IUiBuilder
this.hasErrorWindow = true;
}
// Only if Draw was successful
if (this.Draw is not null && snapshot is not null)
ImGuiManagedAsserts.ReportProblems(this.namespaceName, snapshot);
this.FrameCount++;
if (DoStats)

View file

@ -2,7 +2,6 @@ using System.Collections.Generic;
using System.Linq;
using Dalamud.Configuration.Internal;
using Dalamud.Interface.Internal.ManagedAsserts;
using ImGuiNET;
using Serilog;
@ -112,12 +111,7 @@ public class WindowSystem
#if DEBUG
// Log.Verbose($"[WS{(hasNamespace ? "/" + this.Namespace : string.Empty)}] Drawing {window.WindowName}");
#endif
var snapshot = ImGuiManagedAsserts.GetSnapshot();
window.DrawInternal(config);
var source = ($"{this.Namespace}::" ?? string.Empty) + window.WindowName;
ImGuiManagedAsserts.ReportProblems(source, snapshot);
}
var focusedWindow = this.windows.FirstOrDefault(window => window.IsFocused && window.RespectCloseHotkey);

View file

@ -58,19 +58,17 @@
<PropertyGroup>
<OutDir>..\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..\..\lib\cimgui\imgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\..\lib\cimgui\imgui;..\..\lib\cimgui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;IMGUI_USER_CONFIG="cimgui_user.h";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
@ -86,7 +84,7 @@
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NDEBUG;CIMGUI_EXPORTS;_WINDOWS;_USRDLL;IMGUI_DISABLE_OBSOLETE_FUNCTIONS=1;IMGUI_USER_CONFIG="cimgui_user.h";%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>

@ -1 +1 @@
Subproject commit a302ebabcca49c2e37711ea14a0b0915d38253b0
Subproject commit fd2377934f2cc007982e21ab82e54b41955cb658