refactor mod to account for changes in the IC10Editor mod interface
This commit is contained in:
@@ -1,66 +1,103 @@
|
||||
namespace Slang;
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using StationeersIC10Editor;
|
||||
|
||||
namespace Slang
|
||||
public static unsafe class SlangExtensions
|
||||
{
|
||||
public static unsafe class SlangExtensions
|
||||
/**
|
||||
* <summary>
|
||||
* This is a helper method to convert a Rust struct for a string pointer
|
||||
* into a C# style string.
|
||||
* </summary>
|
||||
*/
|
||||
public static string AsString(this Vec_uint8_t vec)
|
||||
{
|
||||
/**
|
||||
* <summary>
|
||||
* This is a helper method to convert a Rust struct for a string pointer
|
||||
* into a C# style string.
|
||||
* </summary>
|
||||
*/
|
||||
public static string AsString(this Vec_uint8_t vec)
|
||||
if (vec.ptr == null || vec.len == UIntPtr.Zero)
|
||||
{
|
||||
if (vec.ptr == null || vec.len == UIntPtr.Zero)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Rust strings are UTF-8. Read bytes from raw pointer.
|
||||
var toReturn = Encoding.UTF8.GetString(vec.ptr, (int)vec.len);
|
||||
|
||||
return toReturn;
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* <summary>This will free a Rust string struct. Because this is a pointer to a struct, this memory
|
||||
* is managed by Rust, therefor it must be freed by Rust
|
||||
* </summary>
|
||||
*/
|
||||
public static void Drop(this Vec_uint8_t vec)
|
||||
// Rust strings are UTF-8. Read bytes from raw pointer.
|
||||
var toReturn = Encoding.UTF8.GetString(vec.ptr, (int)vec.len);
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* <summary>This will free a Rust string struct. Because this is a pointer to a struct, this memory
|
||||
* is managed by Rust, therefor it must be freed by Rust
|
||||
* </summary>
|
||||
*/
|
||||
public static void Drop(this Vec_uint8_t vec)
|
||||
{
|
||||
Ffi.free_string(vec);
|
||||
}
|
||||
|
||||
/**
|
||||
* <summary>This helper converts a Rust vec to a C# List. This handles freeing the
|
||||
* Rust allocation after the List is created, there is no need to Drop this memory.
|
||||
* </summary>
|
||||
*/
|
||||
public static Line ToLine(this Vec_FfiToken_t vec, string sourceText)
|
||||
{
|
||||
var list = new Line(sourceText);
|
||||
|
||||
var currentPtr = vec.ptr;
|
||||
|
||||
// Iterate through the raw memory array
|
||||
for (int i = 0; i < (int)vec.len; i++)
|
||||
{
|
||||
Ffi.free_string(vec);
|
||||
var token = currentPtr[i];
|
||||
|
||||
var color = GetColorForKind(token.token_kind);
|
||||
|
||||
int colIndex = token.column;
|
||||
if (colIndex < 0)
|
||||
colIndex = 0;
|
||||
|
||||
var semanticToken = new SemanticToken(
|
||||
0,
|
||||
colIndex,
|
||||
token.length,
|
||||
color,
|
||||
token.token_kind
|
||||
);
|
||||
|
||||
string errMsg = token.error.AsString();
|
||||
if (!string.IsNullOrEmpty(errMsg))
|
||||
{
|
||||
semanticToken.IsError = true;
|
||||
semanticToken.Data = errMsg;
|
||||
semanticToken.Color = ICodeFormatter.ColorError;
|
||||
}
|
||||
list.AddToken(semanticToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* <summary>This helper converts a Rust vec to a C# List. This handles freeing the
|
||||
* Rust allocation after the List is created, there is no need to Drop this memory.
|
||||
* </summary>
|
||||
*/
|
||||
public static Line AsList(this Vec_FfiToken_t vec)
|
||||
Ffi.free_ffi_token_vec(vec);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static uint GetColorForKind(uint kind)
|
||||
{
|
||||
switch (kind)
|
||||
{
|
||||
var list = new Line();
|
||||
list.Capacity = (int)vec.len;
|
||||
|
||||
var currentPtr = vec.ptr;
|
||||
|
||||
// Iterate through the raw memory array
|
||||
for (int i = 0; i < (int)vec.len; i++)
|
||||
{
|
||||
// Dereference pointer to get the struct at index i
|
||||
FfiToken_t token = currentPtr[i];
|
||||
|
||||
var newToken = new Token(token.text.AsString(), token.column);
|
||||
|
||||
list.Add(newToken);
|
||||
}
|
||||
|
||||
Ffi.free_ffi_token_vec(vec);
|
||||
|
||||
return list;
|
||||
case 1:
|
||||
return SlangFormatter.ColorInstruction; // Keyword
|
||||
case 2:
|
||||
return SlangFormatter.ColorDefault; // Identifier
|
||||
case 3:
|
||||
return SlangFormatter.ColorNumber; // Number
|
||||
case 4:
|
||||
return SlangFormatter.ColorString; // String
|
||||
case 5:
|
||||
return SlangFormatter.ColorInstruction; // Boolean
|
||||
case 6:
|
||||
return SlangFormatter.ColorDefault; // Symbol
|
||||
default:
|
||||
return SlangFormatter.ColorDefault;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,17 +83,17 @@ public unsafe partial class Ffi {
|
||||
slice_ref_uint16_t input);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 104)]
|
||||
[StructLayout(LayoutKind.Sequential, Size = 64)]
|
||||
public unsafe struct FfiToken_t {
|
||||
public Vec_uint8_t text;
|
||||
|
||||
public Vec_uint8_t tooltip;
|
||||
|
||||
public Vec_uint8_t error;
|
||||
|
||||
public Vec_uint8_t status;
|
||||
|
||||
public Int32 column;
|
||||
|
||||
public Int32 length;
|
||||
|
||||
public UInt32 token_kind;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
namespace Slang;
|
||||
|
||||
using StationeersIC10Editor;
|
||||
|
||||
namespace Slang
|
||||
public class SlangFormatter : ICodeFormatter
|
||||
{
|
||||
public class SlangFormatter : ICodeFormatter
|
||||
public static readonly uint ColorInstruction = ColorFromHTML("#ffff00");
|
||||
public static readonly uint ColorString = ColorFromHTML("#ce9178");
|
||||
|
||||
public override Line ParseLine(string line)
|
||||
{
|
||||
public override Line ParseLine(string line)
|
||||
return Marshal.TokenizeLine(line);
|
||||
}
|
||||
|
||||
public override string Compile()
|
||||
{
|
||||
if (Marshal.CompileFromString(this.Lines.RawText, out string compiled))
|
||||
{
|
||||
return Marshal.TokenizeLine(line);
|
||||
return compiled;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,157 @@
|
||||
namespace Slang;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using StationeersIC10Editor;
|
||||
|
||||
namespace Slang
|
||||
public static class Marshal
|
||||
{
|
||||
public static class Marshal
|
||||
{
|
||||
public static unsafe Line TokenizeLine(string source)
|
||||
{
|
||||
if (String.IsNullOrEmpty(source))
|
||||
{
|
||||
return new Line();
|
||||
}
|
||||
private static IntPtr _libraryHandle = IntPtr.Zero;
|
||||
|
||||
fixed (char* ptrString = source)
|
||||
{
|
||||
var input = new slice_ref_uint16_t
|
||||
{
|
||||
ptr = (ushort*)ptrString,
|
||||
len = (UIntPtr)source.Length,
|
||||
};
|
||||
return Ffi.tokenize_line(input).AsList();
|
||||
}
|
||||
[DllImport("kernel32", SetLastError = true, CharSet = CharSet.Ansi)]
|
||||
private static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
private static extern bool FreeLibrary(IntPtr hModule);
|
||||
|
||||
private static bool EnsureLibLoaded()
|
||||
{
|
||||
if (_libraryHandle != IntPtr.Zero)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public static unsafe bool CompileFromString(string inputString, out string compiledString)
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrEmpty(inputString))
|
||||
_libraryHandle = LoadLibrary(ExtractNativeLibrary(Ffi.RustLib));
|
||||
CodeFormatters.RegisterFormatter("Slang", () => new SlangFormatter(), true);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
L.Error($"Failed to init slang compiler: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Init()
|
||||
{
|
||||
return EnsureLibLoaded();
|
||||
}
|
||||
|
||||
public static bool Destroy()
|
||||
{
|
||||
if (_libraryHandle == IntPtr.Zero)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FreeLibrary(_libraryHandle);
|
||||
_libraryHandle = IntPtr.Zero;
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
L.Warning($"Unable to free handle to slang compiler's dll. {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe Line TokenizeLine(string source)
|
||||
{
|
||||
if (String.IsNullOrEmpty(source))
|
||||
{
|
||||
return new Line(source);
|
||||
}
|
||||
|
||||
if (!EnsureLibLoaded())
|
||||
{
|
||||
return new Line(source);
|
||||
}
|
||||
|
||||
fixed (char* ptrString = source)
|
||||
{
|
||||
var input = new slice_ref_uint16_t
|
||||
{
|
||||
compiledString = String.Empty;
|
||||
return false;
|
||||
ptr = (ushort*)ptrString,
|
||||
len = (UIntPtr)source.Length,
|
||||
};
|
||||
return Ffi.tokenize_line(input).ToLine(source);
|
||||
}
|
||||
}
|
||||
|
||||
public static unsafe bool CompileFromString(string inputString, out string compiledString)
|
||||
{
|
||||
if (String.IsNullOrEmpty(inputString))
|
||||
{
|
||||
compiledString = String.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureLibLoaded())
|
||||
{
|
||||
compiledString = String.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
fixed (char* ptrString = inputString)
|
||||
{
|
||||
var input = new slice_ref_uint16_t
|
||||
{
|
||||
ptr = (ushort*)ptrString,
|
||||
len = (UIntPtr)inputString.Length,
|
||||
};
|
||||
|
||||
var result = Ffi.compile_from_string(input);
|
||||
try
|
||||
{
|
||||
if ((ulong)result.len < 1)
|
||||
{
|
||||
compiledString = String.Empty;
|
||||
return false;
|
||||
}
|
||||
compiledString = result.AsString();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
result.Drop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string ExtractNativeLibrary(string libName)
|
||||
{
|
||||
string destinationPath = Path.Combine(Path.GetTempPath(), libName);
|
||||
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
using (Stream stream = assembly.GetManifestResourceStream(libName))
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
L.Error(
|
||||
$"{libName} not found. This means it was not embedded in the mod. Please contact the mod author!"
|
||||
);
|
||||
return "";
|
||||
}
|
||||
|
||||
fixed (char* ptrString = inputString)
|
||||
try
|
||||
{
|
||||
var input = new slice_ref_uint16_t
|
||||
using (FileStream fileStream = new FileStream(destinationPath, FileMode.Create))
|
||||
{
|
||||
ptr = (ushort*)ptrString,
|
||||
len = (UIntPtr)inputString.Length,
|
||||
};
|
||||
|
||||
var result = Ffi.compile_from_string(input);
|
||||
try
|
||||
{
|
||||
if ((ulong)result.len < 1)
|
||||
{
|
||||
compiledString = String.Empty;
|
||||
return false;
|
||||
}
|
||||
compiledString = result.AsString();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
result.Drop();
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
return destinationPath;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
L.Warning($"Could not overwrite {libName} (it might be in use): {e.Message}");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using BepInEx;
|
||||
using HarmonyLib;
|
||||
using StationeersIC10Editor;
|
||||
|
||||
namespace Slang
|
||||
{
|
||||
@@ -44,6 +41,8 @@ namespace Slang
|
||||
public const string PluginGuid = "com.biddydev.slang";
|
||||
public const string PluginName = "Slang";
|
||||
|
||||
private Harmony? _harmony;
|
||||
|
||||
private static Regex? _slangSourceCheck = null;
|
||||
|
||||
private static Regex SlangSourceCheck
|
||||
@@ -89,44 +88,28 @@ namespace Slang
|
||||
private void Awake()
|
||||
{
|
||||
L.SetLogger(Logger);
|
||||
this._harmony = new Harmony(PluginGuid);
|
||||
L.Info("slang loaded");
|
||||
|
||||
if (ExtractNativeDll(Ffi.RustLib))
|
||||
// If we failed to load the compiler, bail from the rest of the patches. It won't matter,
|
||||
// as the compiler itself has failed to load.
|
||||
if (!Marshal.Init())
|
||||
{
|
||||
var harmony = new Harmony(PluginGuid);
|
||||
harmony.PatchAll();
|
||||
CodeFormatters.RegisterFormatter("slang", () => new SlangFormatter(), true);
|
||||
return;
|
||||
}
|
||||
|
||||
this._harmony.PatchAll();
|
||||
}
|
||||
|
||||
private bool ExtractNativeDll(string fileName)
|
||||
private void OnDestroy()
|
||||
{
|
||||
string destinationPath = Path.Combine(Path.GetDirectoryName(Info.Location), fileName);
|
||||
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
using (Stream stream = assembly.GetManifestResourceStream(fileName))
|
||||
if (Marshal.Destroy())
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
L.Error(
|
||||
$"{Ffi.RustLib} not found. This means it was not embedded in the mod. Please contact the mod author!"
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (FileStream fileStream = new FileStream(destinationPath, FileMode.Create))
|
||||
{
|
||||
stream.CopyTo(fileStream);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
L.Warning($"Could not overwrite {fileName} (it might be in use): {e.Message}");
|
||||
return false;
|
||||
}
|
||||
L.Info("FFI references cleaned up.");
|
||||
}
|
||||
if (this._harmony is not null)
|
||||
{
|
||||
this._harmony.UnpatchSelf();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user