Magic Nodes

Managed to achieve some progress invoking Custom Events within parent blueprints from a C# script.

These custom events, in this test, are executed from lines 64 and 65. From calls to “Invoke()” methods;
It’s a start (my blueprint contains two CS nodes running the same script, so the print happens twice):

https://i.imgur.com/3NA20q6.png

I’m still wondering if this is a good / friendly syntax to pass array references as pins to C# script…

I’m open to suggestions and criticism :

Got type marshalling working for Map properties…

And finally got the off-node compiler working. This was a big nightmare, but seems to be working now :o

https://i.imgur.com/l6m9cfH.png

This one took much longer than I hoped for (somewhere around 14,000 lines of new C++ code),
but C# runtime now can see and recognizes arrays of Actor references in the Blueprint’s Virtual Machine.

That means when an array is passed to a C# MagicNode, the values of the array are NOT copied over to the C# world. Instead, the C# node can access the memory address of that array (as ref) and make changes to it then return to the Blueprint VM the results of its changes, as a new memory address in the VM, without copying values to C# world and back…
Making copies of all data passed to C# would imply a very bad runtime performance, so took me a while to figure out the good way to avoid doing that.
This is still a preparation step before creating a GameFramework library, because internals of Unreal’s virtual machine are reeaallly complicated:

https://i.imgur.com/qXfiZpQ.png

Dude, this is phenominal; black magic for sure, but phenominal. Is the C# version of Magic Node on the Marketplace, or is it just C++? Awesome work!

Thanks @Dijon

The C# plugin is not published yet.
I am still debugging and benchmarking GETers and SETers (the translators that push and pull data between C# and Blueprint Virtual Machine);
This is very laborious and tedious work, but once this critical step is out of the way, it’s just a matter of creating a “C# GameFramework” for Unreal which I did not begin yet.
Currently I am debugging MAP Getters, value Settters I have already done for every common blueprint data type:

When a CS GameFramework is built, I will release it on the Unreal’s Marketplace also free like the C++ MagicNodes plugin.
I don’t have any ETA, because I only work on this on weekends and sometimes late hours at home.

No problem with the time, I completely understand. Anyone who asks for an estimated release date of a software project likely has never tried to write code. I like ID’s philosophy: It’s done when it’s done. :slight_smile:

I’m really excited to try it out, so when the time comes, I’ll be one of the early adopters.

Yesterday I finally put theory to practice;
Successfully implemented a generic Array.Add<T>(…) in C#.

Since I actually make scripts hold a pointer to the value of an array pin, instead of copying all array values, I’ve managed to actually add a new value to a blueprint array, from the C# script with a NET generic method; and it worked! It’s pretty cool to mess around with memory addresses in Blueprint’s VM lol

Congrats on cracking another difficult problem. I’m also really glad you’re still working on this. I don’t have .NET 6 on my computer now, but if you need beta testers, I’ll install the latest preview. I’m really excited about this, but hopefully it doesn’t show :wink:

Thanks Dijon!

But this isn’t even Beta yet lmao.
I usually do a full code review before sharing plugins (to make sure it will not explode somebody’s PC), but this one is really big, I have done zero code reviews yet and I am sure there are nasty bugs hiding somewhere…

Basically I still have to build a decent C# API, since December last year I have been exclusively working on the C++ side of the C# engine and the IntelliNONsense™ code completion.

It’s difficult to build a decent code completion system, basically I have to create an AI that understands C# while user is typing. I am still in a battle with this one.

To keep my sanity I am aiming to work on single platforms one at a time, for now I am building Mono for Windows only. So I hope to have the C# side built still this year, and I will start to build small games with it, when it happens I will share a beta build! (for Windows for now)

Things could go faster if I’d use auto generated script bindings, but I do not like to do that for several reasons. I prefer to hand-pick only what is actually needed.

1 Like

I have a couple of questions regarding the C++ version:

In the walk-through on your web page, near the end, you indicate that if the node doesn’t need to be latent, then implementation is easier. Would I be correct in assuming that a header file is not required for this type of node?

I’m having a hard time deciphering how to distinguish between input and output nodes in the Execute function. Am I just missing something obvious?

Usually the only thing you have to do is to include the runtime module to your list of dependencies in your Build.cs file and nodes will compile successfully.

But you always have to declare the header with the ‘IMGC’ macro…
This macro will make a call to the UFUNCTION() macro and configure a default setup for the Execute entry to be callable by Blueprints. If you configure your Asset Browser’s View Options to Show Plugin Content you can find a few examples like this print node:

.H

    public:

    IMGC() static void Execute (
    	UObject* Context,
    	const TArray<FString> &Array
    );

.CPP

    void FSelf::Execute (
    	UObject* Context,
    	const TArray<FString> &Array
    ) {
    	for (auto &Item : Array) {
    		LOG_MGC(Item);
    		LOG_MGC(1.0f,FColor::Blue,Item);
    	}
    }

If you don’t declare the header your function will not be visible to the Virtual Machine.


Do you mean input pins?!
If so, Unreal checks if an input is const or not, if is value or reference type, etc.
For example, this will be an array as input reference:

IMGC() static void Execute (
	const TArray<FString> &ArrayIN
)

While this will be a node with an array as output pin:

IMGC() static void Execute (
	TArray<FString> & ArrayOut
)

NOTE:

  • Keep in mind that is illustrative, every Execute() function of a node must contain a
UObject* Context

input pin (it is there, but hidden by the engine).

1 Like

If you enable plugin content on Asset Browser, you will be able to pick a few example nodes:

1 Like

Wow, talk about a detailed answer! I get all of it except for the input and output pins. To someone just starting with C++, there doesn’t seem to be a pattern for what makes an input pin vs an output pin (const, UPARAM, pointer, pointer to a reference, just a reference). Is there anything that you can add to make this a little clearer? Thanks for all of the help.

lol that’s just how C++ is;
The language gives you a dozen different ways to do the exactly same thing.

This is why so many people email me all the time asking for a C# node.

{get; set} is way cleaner to tell what is input and what is output.

Do you have any general guidelines on how to identify a pin as input or output? At one point I thought “pointers in and references out” but quickly found that doesn’t hold. I’m quite curious as well to know what UPARAM does. Is there a “for dummies” guide on any of this? I’d like to use your plugin to get my first taste of C++ but I don’t see a pattern for input and output pins. I’ll take a look at your examples in the plugin to see if it helps.

I’m determined to bash my head against this until I figure it out :slight_smile:

UPARAM(ref) is hackery by Epic Games.
We use this when you want to input a property by reference, and maybe call methods that might modify the state of the property.

const Type& someType does the same (an input), but depending on what you do in c++, the compiler is going the scream because the param is marked as a ‘constant’.

If there’s no UPARAM(ref) then the value is an output pin when declared as non constant reference.

Non constant references will always be treated as output pins.
Types passed by value are always inputs.

What about something like Character* _Character? IIRC, you use this as an input in one of your examples.

I really appreciate your patience. Hopefully others are learning from this as well. I never imagined that I’d be excited about trying C++ :wink:

Think of pointers as a box that contains a number;
This box is a value, contains something inside:

ACharacter* MyCharacterA // ← there’s a number in there: 14687354
ACharacter* MyCharacterB // ← there’s a number in there: 86587531

The number inside the box is the address of the thing that pointer is pointing to, in this case a ACharacter entity. The address number in the box is where that character lives in the RAM of your computer.


A reference is a label you give the actual ACharacter, or to that box with the address of the ACharacter.
When you access a label, a reference, you are telling C++ compiler to use the object you named that label. The compiler just picks the box and use it…

When you access a box, a pointer, the compiler will pick the box, read the number inside, then it will go all the way to your RAM and finally access the ACharacter living in that address.

You can have several boxes, containing the address of another box, with the address of another box, and so on… Up to the box which finally contains the address of the ACharacter entity.
Your program will visit and open each box until it goes to the one that gives the address of that entity.


But references contain no value. They just give identity to something that actually exist.
Because they hold no value, Unreal Build Tool assume that you want an output pin that is accessing something “named” that label.

You can also give a label to a pointer, not just objects, so:

ACharacter* & _Character; // ← there’s no number, no address, this is not a box.

Above, you have the obligation to tell the compiler what is the object that label is representing.
That is something of type ACharacter* you gave this label while in the body of this function.
The actual ACharacter* is found outside the function. You can’t change the label you gave the box.


const ACharacter* & _Character; // ← same as above!

But here, you promise that you won’t change what is inside the box, once the label is attached to a valid box, a pointer. You also can’t change the label you gave the box, not only that, you cannot change the address number inside the box either.

Unreal Build Tool assume that you want to use this label as an input.
But it is going to scream at you if you never give a box for this label to be slapped onto, while in the body of the function. Because labels have no value… Unreal is going to tell you:

The current value of the '_Character ' pin is invalid: Reference inputs must have an input wired into them.

Let’s say you used MyCharacterA as input, _Character is now its local label…
Now let’s say you’re done with MyCharacterA and want work on MyCharacterB.

You do:

_Character = MyCharacterB;

But you get an error because you’ve promised you would not change the number inside MyCharacterA box. For the compiler, _Character is simply another name for MyCharacterA.

UPARAM(ref) tells Unreal that you want a label as input, but you will not promise that you will never change the number address in the actual box or the state of MyCharacterA.

Thank you for such a detailed answer. I think that now I know enough to be dangerous :upside_down_face:

I’ll practice for a bit using your example code as a guide. I don’t suppose the C# version of the plugin will be ready anytime soon… :wink:

Awesome support, btw