Skip to content

Commit

Permalink
Add Excel scripting back
Browse files Browse the repository at this point in the history
  • Loading branch information
NotNite committed Sep 7, 2024
1 parent 2ecd760 commit fc833ad
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 7 deletions.
1 change: 1 addition & 0 deletions Alpha/Alpha.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.11.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0"/>

<PackageReference Include="Lumina" Version="4.1.0"/>
Expand Down
123 changes: 116 additions & 7 deletions Alpha/Gui/Windows/ExcelWindow.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
using System.Diagnostics;
using System.Numerics;
using System.Reflection;
using Alpha.Services;
using Alpha.Services.Excel;
using Alpha.Services.Excel.Cells;
using ImGuiNET;
using Lumina;
using Lumina.Data.Structs.Excel;
using Lumina.Excel;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.Extensions.Logging;
using Serilog;

namespace Alpha.Gui.Windows;

[Window("Excel")]
public class ExcelWindow : Window, IDisposable {
public class ExcelWindow : Window, IDisposable {
private RawExcelSheet? selectedSheet;
private Dictionary<string, Dictionary<uint, (uint, uint?)>>
rowMappings = new(); // Working around a Lumina bug to map index to row
Expand All @@ -33,6 +36,10 @@ public class ExcelWindow : Window, IDisposable {

private Dictionary<RawExcelSheet, Dictionary<(int, int), CachedCell>> cellCache = new();

private CancellationTokenSource? scriptToken;
private CancellationTokenSource? sidebarToken;

Check warning on line 40 in Alpha/Gui/Windows/ExcelWindow.cs

View workflow job for this annotation

GitHub Actions / Windows nightly builds

The field 'ExcelWindow.sidebarToken' is never used

Check warning on line 40 in Alpha/Gui/Windows/ExcelWindow.cs

View workflow job for this annotation

GitHub Actions / Windows nightly builds

The field 'ExcelWindow.sidebarToken' is never used
private string? scriptError;

private readonly ExcelService excel;
private readonly Config config;
private readonly GameDataService gameData;
Expand Down Expand Up @@ -136,10 +143,10 @@ private void DrawSidebar() {
private void DrawContentFilter(float width) {
ImGui.SetNextItemWidth(width);

/*var shouldRed = this.scriptError is not null;
var shouldRed = this.scriptError is not null;
if (shouldRed) ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0f, 0f, 1f));
var shouldOrange = this.scriptToken is not null && !shouldRed;
if (shouldOrange) ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.5f, 0f, 1f));*/
if (shouldOrange) ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.5f, 0f, 1f));

if (ImGui.InputText("##ExcelContentFilter", ref this.contentFilter, 1024,
ImGuiInputTextFlags.EnterReturnsTrue)) {
Expand All @@ -152,7 +159,7 @@ private void DrawContentFilter(float width) {
this.ResolveContentFilter();
}

/*if (shouldRed) {
if (shouldRed) {
ImGui.PopStyleColor();
if (ImGui.IsItemHovered()) {
ImGui.BeginTooltip();
Expand All @@ -170,7 +177,7 @@ private void DrawContentFilter(float width) {
+ "To stop the script, empty or right click the input box.");
ImGui.EndTooltip();
}
}*/
}
}

public void OpenSheet(string sheetName, int? scrollTo = null) {
Expand Down Expand Up @@ -249,6 +256,14 @@ private void ResolveSidebarFilter() {
private void ResolveContentFilter() {
Log.Debug("Resolving content filter...");

if (this.scriptToken is not null && !this.scriptToken.IsCancellationRequested) {
this.scriptToken.Cancel();
this.scriptToken.Dispose();
this.scriptToken = null;
}

this.scriptError = null;

if (string.IsNullOrEmpty(this.contentFilter)) {
this.filteredRows = null;
return;
Expand All @@ -259,15 +274,18 @@ private void ResolveContentFilter() {
return;
}

// TODO: also probably need to wait for the task
if (this.filterCts is not null) {
this.filterCts.Cancel();
this.filterCts.Dispose();
this.filterCts = null;
}

this.filterCts = new CancellationTokenSource();
this.ContentFilterSimple(this.contentFilter);
if (this.contentFilter.StartsWith('$')) {
this.ContentFilterScript(this.contentFilter[1..]);
} else {
this.ContentFilterSimple(this.contentFilter);
}

this.itemHeight = 0;
Log.Debug("Filter resolved!");
Expand Down Expand Up @@ -306,6 +324,97 @@ private void ContentFilterSimple(string filter) {
}, this.filterCts!.Token);
}

private void ContentFilterScript(string script) {
this.scriptError = null;
this.filteredRows = new();

// picked a random type for this, doesn't really matter
var luminaTypes = Assembly.GetAssembly(typeof(Lumina.Excel.GeneratedSheets.Addon))?.GetTypes()
.Where(t => t.Namespace == "Lumina.Excel.GeneratedSheets")
.ToList();
var sheets = luminaTypes?
.Where(t => t.GetCustomAttributes(typeof(SheetAttribute), false).Length > 0)
.ToDictionary(t => ((SheetAttribute) t.GetCustomAttributes(typeof(SheetAttribute), false)[0]).Name);

Type? sheetRow = null;
if (sheets?.TryGetValue(this.selectedSheet!.Name, out var sheetType) == true) {
sheetRow = sheetType;
}

// GameData.GetExcelSheet<T>();
var getExcelSheet = typeof(GameData).GetMethod("GetExcelSheet", Type.EmptyTypes);
var genericMethod = sheetRow is not null ? getExcelSheet?.MakeGenericMethod(sheetRow) : null;
var sheetInstance = genericMethod?.Invoke(this.gameData.GameData, []);

var ct = new CancellationTokenSource();
Task.Run(async () => {
try {
var globalsType = sheetRow != null
? typeof(ExcelScriptingGlobal<>).MakeGenericType(sheetRow)
: null;
var expr = CSharpScript.Create<bool>(script, globalsType: globalsType);
expr.Compile(ct.Token);

for (var i = 0u; i < this.selectedSheet!.RowCount; i++) {
if (ct.IsCancellationRequested) {
this.logger.LogDebug("Filter script cancelled - aborting");
return;
}

var row = this.GetRow(this.selectedSheet, this.rowMappings[this.selectedSheet.Name], i);
if (row is null) continue;

async void SimpleEval() {
try {
var res = await expr.RunAsync(cancellationToken: ct.Token);
if (res.ReturnValue) this.filteredRows?.Add(i);
} catch (Exception e) {
this.scriptError = e.Message;
}
}

if (sheetRow is null) {
SimpleEval();
} else {
object? instance;
if (row.SubRowId == 0) {
// sheet.GetRow(row.RowId);
var getRow = sheetInstance?.GetType().GetMethod("GetRow", [typeof(uint)]);
instance = getRow?.Invoke(sheetInstance, [row.RowId]);
} else {
// sheet.GetRow(row.RowId, row.SubRowId);
var getRow = sheetInstance?.GetType()
.GetMethod("GetRow", [typeof(uint), typeof(uint)]);
instance = getRow?.Invoke(sheetInstance, [row.RowId, row.SubRowId]);
}

// new ExcelScriptingGlobal<ExcelRow>(sheet, row);
var excelScriptingGlobal = typeof(ExcelScriptingGlobal<>).MakeGenericType(sheetRow);
var globals = Activator.CreateInstance(excelScriptingGlobal, sheetInstance, instance);
if (globals is null) {
SimpleEval();
} else {
try {
var res = await expr.RunAsync(globals, ct.Token);
if (res.ReturnValue) this.filteredRows?.Add(i);
} catch (Exception e) {
this.scriptError = e.Message;
}
}
}
}
} catch (Exception e) {
this.logger.LogError(e, "Filter script failed");
this.scriptError = e.Message;
}

this.logger.LogDebug("Filter script finished");
this.scriptToken = null;
}, ct.Token);

this.scriptToken = ct;
}

// Building a new RowParser every time is probably not the best idea, but doesn't seem to impact performance that hard
private RowParser? GetRow(
RawExcelSheet sheet,
Expand Down
17 changes: 17 additions & 0 deletions Alpha/Services/Excel/ExcelScriptingGlobal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Lumina.Excel;

namespace Alpha.Services.Excel;

public class ExcelScriptingGlobal<T> where T : ExcelRow {
public ExcelSheet<T> Sheet { get; }
public T Row { get; }
public uint RowId { get; }
public uint SubRowId { get; }

public ExcelScriptingGlobal(ExcelSheet<T> sheet, T row) {
Sheet = sheet;
Row = row;
RowId = row.RowId;
SubRowId = row.SubRowId;
}
}

0 comments on commit fc833ad

Please sign in to comment.