wip -- marshal UTF16 string from C# to Rust to avoid GC in C#
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using StationeersIC10Editor;
|
using StationeersIC10Editor;
|
||||||
|
|
||||||
@@ -27,9 +26,11 @@ namespace Slang
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Convert Rust Token Vector to C# List
|
// 2. Convert Rust Token Vector to C# List
|
||||||
public static List<Token> AsList(this Vec_FfiToken_t vec)
|
public static Line AsList(this Vec_FfiToken_t vec)
|
||||||
{
|
{
|
||||||
var list = new List<Token>((int)vec.len);
|
var list = new Line();
|
||||||
|
list.Capacity = (int)vec.len;
|
||||||
|
|
||||||
var currentPtr = vec.ptr;
|
var currentPtr = vec.ptr;
|
||||||
|
|
||||||
// Iterate through the raw memory array
|
// Iterate through the raw memory array
|
||||||
@@ -6,7 +6,7 @@ namespace Slang
|
|||||||
{
|
{
|
||||||
public override Line ParseLine(string line)
|
public override Line ParseLine(string line)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException();
|
return Marshal.TokenizeLine(line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
30
csharp_mod/Marshal.cs
Normal file
30
csharp_mod/Marshal.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using StationeersIC10Editor;
|
||||||
|
|
||||||
|
namespace Slang
|
||||||
|
{
|
||||||
|
public static class Marshal
|
||||||
|
{
|
||||||
|
public static unsafe Line TokenizeLine(string input)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrEmpty(input))
|
||||||
|
{
|
||||||
|
return new Line();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the string is a null terminated string
|
||||||
|
if (input[input.Length - 1] != '\0')
|
||||||
|
{
|
||||||
|
input += '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
var strBytes = Encoding.UTF8.GetBytes(input);
|
||||||
|
|
||||||
|
fixed (byte* ptrString = strBytes)
|
||||||
|
{
|
||||||
|
return Ffi.tokenize_line(ptrString).AsList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using BepInEx;
|
using BepInEx;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
using StationeersIC10Editor;
|
||||||
|
|
||||||
namespace Slang
|
namespace Slang
|
||||||
{
|
{
|
||||||
@@ -103,6 +102,7 @@ namespace Slang
|
|||||||
ExtractNativeDll("slang.dll");
|
ExtractNativeDll("slang.dll");
|
||||||
var harmony = new Harmony(PluginGuid);
|
var harmony = new Harmony(PluginGuid);
|
||||||
harmony.PatchAll();
|
harmony.PatchAll();
|
||||||
|
CodeFormatters.RegisterFormatter("slang", () => new SlangFormatter(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExtractNativeDll(string fileName)
|
private void ExtractNativeDll(string fileName)
|
||||||
@@ -124,7 +124,7 @@ pub enum System {
|
|||||||
/// Loads a LogicType from all connected network devices, aggregating them via a
|
/// Loads a LogicType from all connected network devices, aggregating them via a
|
||||||
/// batchMode
|
/// batchMode
|
||||||
/// ## In Game
|
/// ## In Game
|
||||||
/// lb r? deviceHash loggicType batchMode
|
/// lb r? deviceHash logicType batchMode
|
||||||
/// ## Examples
|
/// ## Examples
|
||||||
/// lb r0 HASH("StructureWallLight") On Minimum
|
/// lb r0 HASH("StructureWallLight") On Minimum
|
||||||
LoadBatch(LiteralOrVariable, Literal, Literal),
|
LoadBatch(LiteralOrVariable, Literal, Literal),
|
||||||
@@ -137,7 +137,7 @@ pub enum System {
|
|||||||
/// Represents a function which stores a setting to all devices that match
|
/// Represents a function which stores a setting to all devices that match
|
||||||
/// the given deviceHash
|
/// the given deviceHash
|
||||||
/// ## In Game
|
/// ## In Game
|
||||||
/// `sb deviceHash logictype r?`
|
/// `sb deviceHash logicType r?`
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// `sb HASH("Doors") Lock 1`
|
/// `sb HASH("Doors") Lock 1`
|
||||||
SetOnDeviceBatched(LiteralOrVariable, Literal, Box<Expression>),
|
SetOnDeviceBatched(LiteralOrVariable, Literal, Box<Expression>),
|
||||||
|
|||||||
@@ -14,11 +14,15 @@ pub struct FfiToken {
|
|||||||
pub column: i32,
|
pub column: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because
|
||||||
|
/// we want to avoid GC. So we pass it to Rust to handle all the memory allocations.
|
||||||
|
/// This should result in the ability to compile many times without triggering frame drops
|
||||||
|
/// from the GC from a `GetBytes()` call on a string in C#.
|
||||||
#[ffi_export]
|
#[ffi_export]
|
||||||
pub fn compile_from_string(input: safer_ffi::char_p::char_p_ref<'_>) -> safer_ffi::String {
|
pub fn compile_from_string(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::String {
|
||||||
let mut writer = BufWriter::new(Vec::new());
|
let mut writer = BufWriter::new(Vec::new());
|
||||||
|
|
||||||
let tokenizer = Tokenizer::from(input.to_str());
|
let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice()));
|
||||||
let parser = Parser::new(tokenizer);
|
let parser = Parser::new(tokenizer);
|
||||||
let compiler = Compiler::new(parser, &mut writer, None);
|
let compiler = Compiler::new(parser, &mut writer, None);
|
||||||
|
|
||||||
@@ -33,10 +37,13 @@ pub fn compile_from_string(input: safer_ffi::char_p::char_p_ref<'_>) -> safer_ff
|
|||||||
// Safety: I know the compiler only outputs valid utf8
|
// Safety: I know the compiler only outputs valid utf8
|
||||||
safer_ffi::String::from(unsafe { String::from_utf8_unchecked(compiled_vec) })
|
safer_ffi::String::from(unsafe { String::from_utf8_unchecked(compiled_vec) })
|
||||||
}
|
}
|
||||||
|
/// C# handles strings as UTF16. We do NOT want to allocate that memory in C# because
|
||||||
|
/// we want to avoid GC. So we pass it to Rust to handle all the memory allocations.
|
||||||
|
/// This should result in the ability to tokenize many times without triggering frame drops
|
||||||
|
/// from the GC from a `GetBytes()` call on a string in C#.
|
||||||
#[ffi_export]
|
#[ffi_export]
|
||||||
pub fn tokenize_line(input: safer_ffi::char_p::char_p_ref<'_>) -> safer_ffi::Vec<FfiToken> {
|
pub fn tokenize_line(input: safer_ffi::slice::Ref<'_, u16>) -> safer_ffi::Vec<FfiToken> {
|
||||||
let tokenizer = Tokenizer::from(input.to_str());
|
let tokenizer = Tokenizer::from(String::from_utf16_lossy(input.as_slice()));
|
||||||
|
|
||||||
let mut tokens = Vec::<FfiToken>::new();
|
let mut tokens = Vec::<FfiToken>::new();
|
||||||
|
|
||||||
@@ -83,6 +90,6 @@ pub fn free_string(s: safer_ffi::String) {
|
|||||||
pub fn generate_headers() -> std::io::Result<()> {
|
pub fn generate_headers() -> std::io::Result<()> {
|
||||||
::safer_ffi::headers::builder()
|
::safer_ffi::headers::builder()
|
||||||
.with_language(safer_ffi::headers::Language::CSharp)
|
.with_language(safer_ffi::headers::Language::CSharp)
|
||||||
.to_file("../csharp_mod/SlangGlue.cs")?
|
.to_file("../csharp_mod/FfiGlue.cs")?
|
||||||
.generate()
|
.generate()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user