Technical Help Needed Integrating UMA into DFU

Discuss coding questions, pull requests, and implementation details.
User avatar
MasonFace
Posts: 543
Joined: Tue Nov 27, 2018 7:28 pm
Location: Tennessee, USA
Contact:

Re: Technical Help Needed Integrating UMA into DFU

Post by MasonFace »

Very cool! If you throw an enum in that script, does it still work? I'm away from my PC right now or I'd try it myself.

User avatar
TheLacus
Posts: 1305
Joined: Wed Sep 14, 2016 6:22 pm

Re: Technical Help Needed Integrating UMA into DFU

Post by TheLacus »

MasonFace wrote: Sat Mar 14, 2020 7:41 pm Very cool! If you throw an enum in that script, does it still work? I'm away from my PC right now or I'd try it myself.
Yes, works fine.

User avatar
MasonFace
Posts: 543
Joined: Tue Nov 27, 2018 7:28 pm
Location: Tennessee, USA
Contact:

Re: Technical Help Needed Integrating UMA into DFU

Post by MasonFace »

Awesome!

I haven't had much time to work on it this weekend, but I've been reading your last several posts over and over trying to understand them the best I can. Bear in mind that I'm not an exceptionally proficient programmer, so I apologize if I continue to ask ignorant questions or ask something that you've already covered. Having said that, I am trying to educate myself as much as possible before I bother you again, but there's a few things that I can't wrap my head around at the moment.



With regards to the .bytes extension: When you said you added the ".bytes" extension, do you mean you literally changed the filename from "test-plugin.dll" to "test-plugin.bytes"? If so, then how does the mod recognize "test-plugin.dll" when you assign it to your assembly in the Start() method?

With regards to the entry point: in your Test class I noticed that you put some of your code in the Start method instead of in the Init method. Is that just so it will log the debug message without having the exit the setup screen in DFU?

With regards to instantiating prefabs with scripts loaded from an assembly: I will need to instantiate a singleton prefab on startup that contains scripts that will be loaded from the assembly. Does the [ImportedComponent] attribute protect classes in assemblies on the prefabs, or will I need to reconstruct the prefabs on startup? Shouldn't be any big deal if I do.

In the meantime, I'm going to do some more reading on reflection to better understand it.

Thanks again.

User avatar
TheLacus
Posts: 1305
Joined: Wed Sep 14, 2016 6:22 pm

Re: Technical Help Needed Integrating UMA into DFU

Post by TheLacus »

MasonFace wrote: Sun Mar 15, 2020 3:13 pm With regards to the .bytes extension: When you said you added the ".bytes" extension, do you mean you literally changed the filename from "test-plugin.dll" to "test-plugin.bytes"? If so, then how does the mod recognize "test-plugin.dll" when you assign it to your assembly in the Start() method?
I changed name.dll to name.dll.bytes. I kept the .dll part so that is immediately recognisable as a plugin when you read the filename; technically the only important thing is that it must end with .bytes in order to be stored by Unity as a binary asset (meaning that it doesn't try to parse it with any specific format).
MasonFace wrote: Sun Mar 15, 2020 3:13 pm With regards to the entry point: in your Test class I noticed that you put some of your code in the Start method instead of in the Init method. Is that just so it will log the debug message without having the exit the setup screen in DFU?
It's a personal preference. I used the static Init() method to make a new instance of my test component and set the mod as ready, then i put the actual mod code inside the Start() method of the component. Start() is called when the component is instantiated (so after Init()) but not necessarily on the same frame. It's fine to do it directly inside Init, just a slightly different design decision with the same result. ;)

Note that i've also set mod.IsReady to true after creating this component. If other mods should be able to communicate with yours, you may want to set it only after the mod is actually to be considered ready. For example you can do it after setting up the message receiver. In general, i think this is something you don't need to care about, unless you know you need it.
MasonFace wrote: Sun Mar 15, 2020 3:13 pm With regards to instantiating prefabs with scripts loaded from an assembly: I will need to instantiate a singleton prefab on startup that contains scripts that will be loaded from the assembly. Does the [ImportedComponent] attribute protect classes in assemblies on the prefabs, or will I need to reconstruct the prefabs on startup? Shouldn't be any big deal if I do.
You would need to add components via script. You are the first one that is using a precompiled assembly in a mod, but if this ends up being a viable way to make mods (and it looks like it is) is decisally worth to support it more, starting with that attribute. Feel free to tell me if there is anything else i can help you with.

EDIT. I wrote some helpers that might be useful:

Code: Select all


// Add assembly to the list of assemblies associated to a mod.
// This allows to retrieve its types with Mod.GetCompiledType()
// which also means support for ImportedComponentAttribute (call this method before loading the prefab!)
private static void AddAssemblyExperimental(Mod mod, Assembly assembly)
{
    FieldInfo fieldInfo = typeof(Mod).GetField("assemblies ", BindingFlags.NonPublic | BindingFlags.Instance);
    var  assemblies = fieldInfo.GetValue(mod) as List<Assembly>;
    assemblies.Add(assembly);
    fieldInfo.SetValue(mod, assemblies);
}

// Call a public static method named "Init()" with the given parameters.
// Init() must be defined inside the class with the given typeFullName.
private static void ExecutInit(Mod mod, string typeFullName, params object[] parameters)
{
    Type type = mod.GetCompiledType(typeFullName);
    MethodInfo methodInfo = type.GetMethod("Init", BindingFlags.Public | BindingFlags.Static);
    methodInfo.Invoke(null, parameters);
}

User avatar
MasonFace
Posts: 543
Joined: Tue Nov 27, 2018 7:28 pm
Location: Tennessee, USA
Contact:

Re: Technical Help Needed Integrating UMA into DFU

Post by MasonFace »

Okay, so since you've created the AddAssemblyExperimental() helper class, I should be able to use the [ImportedComponent] attribute on all the classes that I need instantiated onto prefabs, correct?

If so, is this the right procedure?
  • Add [ImportedComponent] attribute to all classes that are attached to a prefab.
  • Build the same scripts (including the ImportedComponent attribute) into an assembly.
  • Write a mod initialization script as usual, but add your helper classes and call AddAssemblyExperimental() before instantiating the prefab.
  • Package the prefab, mod initialization script, and assembly into the .dfmod.
  • Build the mod and of course move the .dfmod to the Streaming Assets/mods folder.
If the above procedure is correct, will there be a conflict if upon deserialization it encounters two of the same type? What I mean is, what happens if a script on a prefab successfully compiles in MCS but is also in the assembly? Will it just load two instances of that type onto the prefab or will one take precedence over the other? The reason I ask is because I intend to just compile all of the UMA scripts into one assembly if possible, with no discrimination between those that have enums and those that don't. If some of the scripts on the prefab compile correctly through MCS and others fail to load, then I figure I might end up with a mess... or, it could totally be a moot point. I don't have enough intuition on the matter to be able to speculate on what might happen. I'd test it myself, but it just now occurred to me while I'm at work.

---------------------------------------

Also, what does the second helper class do? Is it just for calling a method on a Type named typeFullName with arguments that match parameters? If so, was "Init()" just an example, or is there something about "Init()" that's necessary? At the moment, I'm viewing this helper class as a template for calling a method in an assembly, so please correct me if I'm wrong.

And thanks again!

User avatar
TheLacus
Posts: 1305
Joined: Wed Sep 14, 2016 6:22 pm

Re: Technical Help Needed Integrating UMA into DFU

Post by TheLacus »

MasonFace wrote: Tue Mar 17, 2020 2:38 pm Okay, so since you've created the AddAssemblyExperimental() helper class, I should be able to use the [ImportedComponent] attribute on all the classes that I need instantiated onto prefabs, correct?

If so, is this the right procedure?
  • Add [ImportedComponent] attribute to all classes that are attached to a prefab.
  • Build the same scripts (including the ImportedComponent attribute) into an assembly.
  • Write a mod initialization script as usual, but add your helper classes and call AddAssemblyExperimental() before instantiating the prefab.
  • Package the prefab, mod initialization script, and assembly into the .dfmod.
  • Build the mod and of course move the .dfmod to the Streaming Assets/mods folder.
This is correct. Mod.assemblies is the list of known assemblies associated to a mod (used by ImportedComponent attribute); it's a private member so i used reflection to add a new value to it. I actually didn't test AddAssemblyExperimental() (i'll try to find the time to do it) but i'm confident it will do the job until proper support is added in game.
MasonFace wrote: Tue Mar 17, 2020 2:38 pm If the above procedure is correct, will there be a conflict if upon deserialization it encounters two of the same type? What I mean is, what happens if a script on a prefab successfully compiles in MCS but is also in the assembly? Will it just load two instances of that type onto the prefab or will one take precedence over the other? The reason I ask is because I intend to just compile all of the UMA scripts into one assembly if possible, with no discrimination between those that have enums and those that don't. If some of the scripts on the prefab compile correctly through MCS and others fail to load, then I figure I might end up with a mess... or, it could totally be a moot point. I don't have enough intuition on the matter to be able to speculate on what might happen. I'd test it myself, but it just now occurred to me while I'm at work.
Two assemblies will be associated with the mod:
- assembly compiled at runtime from raw .cs files (you only need one for initilization).
- pre-compiled assembly (the .dll file)
Each class type must be defined in a single assembly.

A component on a prefab is a reference to a class instance (with a certain type). When the prefab is loaded from resources, these types are seeked from known assemblies and instantiated on the gameobject. I might have misunderstood your question, so tell me if that's the case. :)
MasonFace wrote: Tue Mar 17, 2020 2:38 pm Also, what does the second helper class do? Is it just for calling a method on a Type named typeFullName with arguments that match parameters? If so, was "Init()" just an example, or is there something about "Init()" that's necessary? At the moment, I'm viewing this helper class as a template for calling a method in an assembly, so please correct me if I'm wrong.

And thanks again!
Yes, you can use any name you want but it must be a public static method.

User avatar
MasonFace
Posts: 543
Joined: Tue Nov 27, 2018 7:28 pm
Location: Tennessee, USA
Contact:

Re: Technical Help Needed Integrating UMA into DFU

Post by MasonFace »

I had some time to work on this again last night and I think I've made some good progress, thanks entirely to your help.

I started with integrating my SpriteWrite utility into DFU using the assembly approach. It took me a little while to find a typo in the helper class (there's a space in the GetField argument for "assemblies "), but once I found that, it was all smooth sailing!

I'm going to try to do the same with the UMA framework soon and hopefully get it going. Then the real fun can begin. :P

Thanks again for your help!

User avatar
MasonFace
Posts: 543
Joined: Tue Nov 27, 2018 7:28 pm
Location: Tennessee, USA
Contact:

Re: Technical Help Needed Integrating UMA into DFU

Post by MasonFace »

Well, SpriteWrite is working in the Unity Editor, but I've ran into a bit of a snag when trying to run it in DFU stand alone.

SpriteWrite needs two new layers setup in the Editor in order to function properly. I have them named "SpriteWrite3D" on layer 15 and "SpriteWrite2D" on layer 16.

From what I've read, there is no way to add a new layer to a built game in runtime.

It would be easiest for me if these two layers were just added to DFU core, but I don't really like that idea.

Instead, could several custom layers be added to DFU core that mods can use?

For example, layers 15-25 could be named CustomLayer0, CustomLayer1, . . . CustomLayer9, then when the mod is initialized it reserves as many layers as it needs and if there are enough layers available, it gets a return message that tells it the layer names and numbers it has reserved. Then perhaps I can set up my layermasks according to that? Could something like that work?

Just something to think on.

Anyhow, I'm going to turn my focus back over to getting UMA integrated, so there is no big rush on getting the layer problem sorted.

Edit: Actually, SpriteWrite needs 3 layers. I forgot the third one called "Invisible" to prevent objects from rendering into the wrong render texture.

User avatar
TheLacus
Posts: 1305
Joined: Wed Sep 14, 2016 6:22 pm

Re: Technical Help Needed Integrating UMA into DFU

Post by TheLacus »

I added support for up to four custom layers. If more are needed, they can be added at a later time. You should be able to assign the layer to gameobjects at runtime (GameObject.layer) and make masks as needed:
mask = (1<< reservedLayer1.Layer) | (1<< reservedLayer2.Layer).

User avatar
MasonFace
Posts: 543
Joined: Tue Nov 27, 2018 7:28 pm
Location: Tennessee, USA
Contact:

Re: Technical Help Needed Integrating UMA into DFU

Post by MasonFace »

Perfect! Thank you!

Post Reply