#How to load in a mod's burst code

1 messages · Page 1 of 1 (latest)

silk crown
#

I’m trying to create a moddable game, where mods provide new code (both managed and burst). Here’s the code in the mod

`[BurstCompile]
public class BurstTest : MonoBehaviour
{

public TMP_Text text;
private int counter = 0;

void Update()
{
    counter++;
    int result = GetTESTNum(counter);
    text.text = "Result: " + result;
}

[BurstCompile]
private static int GetTESTNum(int input) 
{
    int result = input * 2; 
    result += 6; 
    result = result / 2;
    return result; //whatever, I just want to PoC that it runs in burst
}

}`

This runs fine if I’m in the mod project, but if I’m in my main project, where I load this in via DLL, I get errors.

Exporting: Runs a build, generates a managed DLL for my mod’s script assembly, as well as a burst DLL

Importing:
AssemblyUtility.registerAssembly(managedDLLPath); BurstRuntime.LoadAdditionalLibrary(burstDLLPath);

Seems to work fine, at least, doesn’t throw any exceptions. However, I get a whole bunch of exceptions from my GetTESTNum method, below:

#

ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index System.Collections.Generic.List1[T].get_Item (System.Int32 index) (at <b11ba2a8fbf24f219f7cc98532a11304>:0) Burst.Compiler.IL.Jit.JitCompilerService.CompileILPPMethod (System.String args) (at <69b504cf34c54d688310e60f1ce29d2e>:0) Burst.Compiler.IL.Jit.JitCompilerService.ProcessCommand (System.String commandName, System.String args) (at <69b504cf34c54d688310e60f1ce29d2e>:0) Burst.Compiler.IL.Jit.JitCompilerService.CompileInternal (System.String fullMethodName, System.String assemblyPaths, System.IntPtr userdata, Unity.Burst.NativeDumpFlags dumpFlags, System.IntPtr compilerCallbackPointer, System.IntPtr logCallBack, System.String compilerFlags) (at <69b504cf34c54d688310e60f1ce29d2e>:0) Unity.Burst.LowLevel.BurstCompilerService:GetDisassembly(MethodInfo, String) Unity.Burst.BurstCompiler:SendRawCommandToCompiler(String) Unity.Burst.CommandBuilder:SendToCompiler() Unity.Burst.BurstCompiler:GetILPPMethodFunctionPointer2(IntPtr, RuntimeMethodHandle, RuntimeTypeHandle) GetTESTNum_00000002$BurstDirectCall:GetFunctionPointerDiscard(IntPtr&)

Followed by

`Assertion failed on expression: 'exception == SCRIPTING_NULL'
UnityEngine.StackTraceUtility:ExtractStackTrace ()
Unity.Burst.BurstCompiler:SendRawCommandToCompiler (string) (at ./Library/PackageCache/com.unity.burst@1.8.18/Runtime/BurstCompiler.cs:787)
Unity.Burst.BurstCompiler/CommandBuilder:SendToCompiler () (at ./Library/PackageCache/com.unity.burst@1.8.18/Runtime/BurstCompiler.cs:92)
Unity.Burst.BurstCompiler:GetILPPMethodFunctionPointer2 (intptr,System.RuntimeMethodHandle,System.RuntimeTypeHandle) (at ./Library/PackageCache/com.unity.burst@1.8.18/Runtime/BurstCompiler.cs:284)
BurstTest/GetTESTNum_00000002$BurstDirectCall:GetFunctionPointerDiscard (intptr&)``

#

Things I’ve checked already

Yes, burst compile is enabled on the mod project, it’s generating a burst dll after all

Apparently burst doesn’t like working in file paths with non-ascii chars, checked for this already

To reiterate, the above code works fine when run from the mod project (so, no importing of DLLs at runtime), I’ve also used the burst inspector to confirm that the GetTESTNum method is in fact running on burst

Tried changing the order of the DLL loading, doesn’t seem to make a difference if managed is loaded before burst, or vice-versa

Tried changing the name of the burst DLL to something different, and loading it under a new name (thought maybe it was conflicting with another burst DLL that was already loaded, idk)

Updated to most recent version of burst (both in the mod and in the main codebase)

All out of ideas! At this point, I don’t know if the problem is in the export side, the import side, or both! If anyone has any thoughts, any help would be super appreciated,

I’d love to get something working this week 🥲

near elk
#

Clarify what exactly you import. Compiled burst assembly?

#

if so: how do you build it?

silk crown
# near elk if so: how do you build it?

Yup good question,

` var bpo = new BuildPlayerOptions()
{
locationPathName = Path.Combine(destination, "__build", "plugin"),
target = BuildTarget.StandaloneWindows64,
options = BuildOptions.BuildScriptsOnly,
};

    BuildPipeline.BuildPlayer(bpo);`

This gives me a build folder with:
My managed DLL in plugin_Data\Managed
A lib_burst_generated.dll in plugin_Data\Plugins\x86_64

I've used this build process for non-burst mods/dlcs for years and never had issue, not sure if there's something different I need to do with burst?

near elk
#

So show which code exactly is failing

#

as in: how are you trying to call this code from loaded mod

silk crown
#

I'm not, the managed code is a monobehaviour, so I'm expecting its Update loop to run automatically, and for that to then make the call to the burst method GetTESTNum

#

That Update method does run, it's just not able to call the burst method

near elk
#

I suppose editor is not really supporting loading such assemblies

#

I remember having very same issue couple years ago

#

so nothing changed since then 😅

silk crown
near elk
silk crown
#

In their example though they have the main codebase manually instantiate the classes from the mod code and then explictely call its functions, rather than letting the mod code run its own MB update loops... that seems really restrictive though, and non-burst code is absolutely fine using its own update loops

near elk
#

not editor though

silk crown
#

What makes you think it works in the build? I can give it a go, but I'd be surprised if its any different

near elk
#

There is no burst JIT in builds

#

so failing code you're having won't exist there

silk crown
#

Right, but the burst code I'm trying to run isnt JIT, it's precompiled into a DLL, so from the mod's point of view, it is in a build, even if the project its loaded into is running in editor

near elk
#

Yeah, but burst runtime in editor is still editor

#

My assumption is that they just didn't test it

#

so precompiled mono assembly that calls bursted code just doesn't assue it's being called from editor runtime

silk crown
#

hmmn, ok, I'll try building the main project and loading in the DLL from the build, perhaps you're right, maybe the fact we're trying to run built burst code in an editor context is upsetting it. Will report back 🫡

near elk
#

in a nutshell, when you mark method with BurstCompile, unity just post processes IL and injects some kind of
if (BurstRuntime.HasMethodAvailable("hardcodedFunctionSignature") { BurstRuntime.CallMethod("hardcodedFunctionSignature"); } else { CallOriginalCode(); }

#

and I just assume that AOT assembly, has something different here, and that's why it fails

silk crown
#

Yeah quite possibly, well, time to watch a big loading bar... fingers crossed!

rustic zenith
#

just to support it in case a modder wants to use it?

near elk
#

@silk crown oooh, try jobs. They have very custom compilation, so maybe they will work fine in editor

near elk
#

without it - my best bet was writing the code in c++ and calling native methods from mode

silk crown
near elk
#

oh well

#

yeah, that errors practically means that burst wasn't setup for job (happens also when you use generic jobs without proper attributes)

silk crown
#

It appears to run in the build though, but with that test script I can't confirm if its actually using burst or not (of course the performance would be the same either way, the test script doesn't really do anything), I'm now trying something more number crunchy to confirm if its actually using burst, or just failing siltently back to the managed code

near elk
#

or other way
[BurstDiscard]
void IsMono(out bool isMono) { isMono = true; }

#

Burst will discard that method during compilation, so it will only run in mono

silk crown
#

I thought [BurstDiscard] meant it wouldn't be compiled in burst, but would still run managed? If I manually invoke that method, without burst, wouldn't it run anyway, but just without burst?

near elk
#

no, it's total discard

silk crown
#

Oh ok cool, so if I call that method without burst... that method then just isn't there? I'm guessing that throws some NotImplementedException or something?

near elk
#

no

#

void BurstedMethod() {
IsMono(out var isMono);
}

#

in Mono it'll be exactly this way

#

in Burst it'll be void BurstedMethod() { bool isMono = default; }

#

so next line you can just add Debug.Log($"Running in Mono - {isMono}")

rustic zenith
#
void MonoMethod() {
  BurstedMethod();
}

[BurstCompile]
private static int BurstedMethod(int v) {
  Discarded();
  return v + 5;
}

[BurstDiscard]
private static void Discarded() {
  Debug.Log("This wasn't discarded");
}

If you call MonoMethod then it shouldn't log, because monomethod should compile into calling the bursted BurstedMethod, which should discard Discarded when bursted

silk crown
near elk
#

allthough, may need to confirm this

#

there is a chance burst compilation may just fail if I missremembered

silk crown
#

Sure thing, thanks both

near elk
#

so most correct way (that will work 100%)
bool isMono = false;
IsMono(ref isMono);
Debug.Log(isMono);

#

(just a note, I edited with ref, not out)

silk crown
#

mmm, doesn't seem to actually be using burst in the build... will try and dig into it more in the build... haven't yet tried the burst test methods above, but built my mod with burst and without burst and tried loading both into the build, both run at the same very low framerate (running in the editor in the mod codebase it runs smooth), so I can only assume the [burstcompile] tags don't actually seem to be making a difference in the build... there are no errors, but seems its ignoring the burst code and running it purely off managed

#

Going to do more digging tomorrow, need to go look at something other than unity for a while

proven crow
#

so to save you a bunch of trouble later

#

to load bursted ISystem and jobs it executes, you have to manually call the normally codegen invoked method EarlyInit

#
                foreach (var mod in mods)
                {
                    var modName = Path.GetFileName(mod);
                    var monoAssembly = Path.Combine(mod, $"{modName}_managed.dll");

                    Debug.Log($"Loading mod: {modName}");

                    if (File.Exists(monoAssembly))
                    {
                        var managedPlugin = Assembly.LoadFile(monoAssembly);

                        foreach (var type in managedPlugin.GetTypes())
                        {
                            var method = type.GetMethod("EarlyInit", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
                            method?.Invoke(null, null);
                        }
                    }

                    var burstedAssembly = Path.Combine(mod, $"{modName}_win_x86_64.dll"); // Burst dll (assuming windows 64bit)
                    if (File.Exists(burstedAssembly))
                    {
                        BurstAssemblies.Add(burstedAssembly);
                    }
                }```
#

Unity usually codegens automatic calls to EarlyInit

silk crown
proven crow
#

Did you ask this in the right place

proven sable
#

Ooops wrong place sorry.

silk crown
#

Looks like burst actually isn't working in my build at all, even without getting into any DLL muppetry, burst code in my core codebase runs fast in the editor, but slow in the build (even though logging shows that burst should be enabled), sigh, idk, will make a separate post as this is kind of a different issue now

near elk
#

I assume it's meant to be done as soon as possible

silk crown
# near elk when exactly are you loading burst?

I'm no longer "loading" burst explicitly at all, I'm now just running with a single build that has burst built into it, with burst scripts executing in the initial scene, seems like it's something to do with build vs editor rather than loading in burst things at runtime from a dll

silk crown
#

Right, so, turns out burst actually was working in the build... been thrown some weird curveballs throughout all this. Here's a recap

I wanted to confirm if burst was actually working in the build. To do this, I created two methods, one with [BurstCompile] and one with [BurstDiscard], both methods did the exact same thing, looping over an array, crunching some numbers, etc. Nothing complex, but defintely expensive given the array size. I used a stopwatch to log out to a UI the time taken for each method to run each update

When running in the editor, the burst version took 35ms, and the managed version took 60ms. All good

When running in the build, both took 35ms. Unexpected... but I tried doing another build with burst compilation turned off, and they both still took 35ms. I assumed this was just the build being faster than the editor, coincidentally ending up at 35ms.

What I now think, is that my burst cache was caching these methods, and that they were running as burst overall, idk. very weird.

Anwyay, back to my main problem, I renamed my DLC's burst DLL and then it started working. Guess the default name "burst_lib_generated" or whatever was already registered in the main codebase, so my importer ignored the call to import it (without throwing an error/warning, it really is a pain when unity fails silently).

near elk
silk crown
#

I was skeptical of the log methods actually behaving as expected, I was dealing with too many unknowns already, using a loop was foolproof at least, and did show a very tangible difference between burst and managed! And yes, I know build is faster, as mentioned 🙂

near elk
#

and you can even test in editor

silk crown
#

You were the one who initially pointed out to me that burst behaves differently in the editor Vs the build! Since the two log the same but behave differently (which I myself am seeing) I opted for testing behaviour, rather than testing logging and just trusting "it's logging that it works in the editor even tho it isn't behaving like it works, I'll assume it'll behave fine in the build"

#

Anyway, it's done and working now