main 43f3c3788f0a cached
30 files
99.0 KB
26.1k tokens
43 symbols
1 requests
Download .txt
Repository: getnamo/GlobalEventSystem-Unreal
Branch: main
Commit: 43f3c3788f0a
Files: 30
Total size: 99.0 KB

Directory structure:
gitextract_fmvxu3bk/

├── .gitignore
├── Content/
│   ├── GenericComponentReceivers/
│   │   ├── BoolGESReceiverComponent.uasset
│   │   ├── FloatGESReceiverComponent.uasset
│   │   ├── IntGESReceiverComponent.uasset
│   │   ├── ObjectGESReceiverComponent.uasset
│   │   ├── RotatorGESReceiverComponent.uasset
│   │   ├── StringGESReceiverComponent.uasset
│   │   ├── TransformGESReceiverComponent.uasset
│   │   └── VectorGESReceiverComponent.uasset
│   ├── Javascript/
│   │   └── GESJsReceiverBpActor.uasset
│   ├── Macros/
│   │   └── GESMacroLibrary.uasset
│   └── Scripts/
│       └── ges/
│           └── gesWrapper.js
├── GlobalEventSystem.uplugin
├── LICENSE
├── README.md
└── Source/
    └── GlobalEventSystem/
        ├── GlobalEventSystem.Build.cs
        ├── Private/
        │   ├── GESBaseReceiverComponent.cpp
        │   ├── GESDataTypes.cpp
        │   ├── GESHandler.cpp
        │   ├── GESHandlerDataTypes.cpp
        │   ├── GESWorldListenerActor.cpp
        │   ├── GlobalEventSystem.cpp
        │   └── GlobalEventSystemBPLibrary.cpp
        └── Public/
            ├── GESBaseReceiverComponent.h
            ├── GESDataTypes.h
            ├── GESHandler.h
            ├── GESHandlerDataTypes.h
            ├── GESWorldListenerActor.h
            ├── GlobalEventSystem.h
            └── GlobalEventSystemBPLibrary.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Visual Studio 2015 user specific files
.vs/

# Visual Studio 2015 database file
*.VC.db

# Compiled Object files
*.slo
*.lo
*.o
*.obj

# Precompiled Headers
*.gch
*.pch

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Fortran module files
*.mod

# Compiled Static libraries
*.lai
*.la
*.a
*.lib

# Executables
*.exe
*.out
*.app
*.ipa

# These project files can be generated by the engine
*.xcodeproj
*.xcworkspace
*.sln
*.suo
*.opensdf
*.sdf
*.VC.db
*.VC.opendb

# Precompiled Assets
SourceArt/**/*.png
SourceArt/**/*.tga

# Binary Files
Binaries/*
Plugins/*/Binaries/*

# Builds
Build/*

# Whitelist PakBlacklist-<BuildConfiguration>.txt files
!Build/*/
Build/*/**
!Build/*/PakBlacklist*.txt

# Don't ignore icon files in Build
!Build/**/*.ico

# Built data for maps
*_BuiltData.uasset

# Configuration files generated by the Editor
Saved/*

# Compiled source files for the engine to use
Intermediate/*
Plugins/*/Intermediate/*

# Cache files for the editor to use
DerivedDataCache/*


================================================
FILE: Content/Scripts/ges/gesWrapper.js
================================================
const uclass = require('uclass')().bind(this, global);

/** 
Wrapper class to enable some passthrough ges binding.
ATM only supports string params and a maximum of 10 bind events
before it overwrites older binds. This is due to the limitation that
UFUNCTIONS have to be defined at design time. Todo: add support for ~100?

Super experimental atm
*/
class GESJsReceiver extends JsOwner.ClassMap['GESJsReceiverBpActor']{
	ctor(){
		this.callbacks = {};
	}
	constructor(){
	}

	OnJsReceive(uniqueId, property){
		this.callbacks[uniqueId](property);
	}

	OnJsReceiveObj(uniqueId, property){
		this.callbacks[uniqueId](property);
	}

	//returns a unique id for potential unbinding
	bind(domain='global.default', event, callback){
		//const uniqueKey = domain + '.' + event;
		const uniqueFunctionId = this.NextUniqueReceiver()['NextUniqueFunction'];

		if(uniqueFunctionId == 'OnJsReceiveOneParam_9'){
			console.warn('GESJsReceiver: Maximum receivers reached!');
		}

		this.callbacks[uniqueFunctionId] = callback;

		this.JsGESBindEvent(domain,
			event,
			uniqueFunctionId);
		return uniqueFunctionId;
	}

	bindToObjCallback(domain='global.default', event, callback, uniqueReceiver = false){
		//const uniqueKey = domain + '.' + event;
		const uniqueFunctionId = this.NextUniqueReceiverObj()['NextUniqueFunction'];

		if(uniqueReceiver && uniqueFunctionId == 'OnJsReceiveOneParamObj_1'){
			console.warn('GESJsReceiver: Maximum obj receivers (1) reached (uniqueReceiver: true)!');
			return '';
		}
		
		if(uniqueFunctionId == 'OnJsReceiveOneParamObj_4'){
			console.warn('GESJsReceiver: Maximum obj receivers reached!');
		}

		this.callbacks[uniqueFunctionId] = callback;

		console.log(`UniqueId is <${uniqueFunctionId}>`)

		this.JsGESBindEvent(domain,
			event,
			uniqueFunctionId);
		return uniqueFunctionId;
	}

	emit(domain='global.default', event, data='', pinned=false){
		if(typeof data === 'string'){
			this.JsGESEmitEventOneParamString(domain, event, data, pinned);
		}
		else{
			this.JsGESEmitEventOneParamObject(domain, event, data, pinned);
		}
	}

	//NB: need to store the unique function id you get from bind
	unbind(domain='global.default', event, uniqueFunctionId){
		this.UnbindEvent(domain, event, uniqueFunctionId);
	}

	wlog(text){
		if(typeof text !== 'string'){
			text = JSON.stringify(text);
		}
		this.emit('global.console', 'log', text);
	}
	unbindAll(){
		this.UnbindAllEvents();
		//GlobalEventSystemBPLibrary.GESUnbindAllEventsForContext(this);
	}
}

const GESJsReceiver_C = uclass(GESJsReceiver);

exports.ges = new GESJsReceiver_C(GWorld, {Z:0});

================================================
FILE: GlobalEventSystem.uplugin
================================================
{
	"FileVersion": 3,
	"Version": 1,
	"VersionName": "0.15.1",
	"FriendlyName": "GlobalEventSystem",
	"Description": "Loosely coupled internal event system.",
	"Category": "Utility",
	"CreatedBy": "getnamo",
	"CreatedByURL": "getnamo.com",
	"DocsURL": "https://github.com/getnamo/global-event-system-ue4",
	"MarketplaceURL": "",
	"SupportURL": "https://github.com/getnamo/global-event-system-ue4/issues",
	"CanContainContent": true,
	"IsBetaVersion": false,
	"Installed": false,
	"Modules": [
		{
			"Name": "GlobalEventSystem",
			"Type": "Runtime",
			"LoadingPhase": "Default"
		}
	]
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 Jan Kaniewski

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# GlobalEventSystem-Unreal
A loosely coupled internal global event system (GES) plugin for the Unreal Engine. Aims to solve cross-map and cross-blueprint communication for reliable and inferable event flow. Should enable a publisher-observer pattern.

[![GitHub release](https://img.shields.io/github/release/getnamo/GlobalEventSystem-Unreal.svg)](https://github.com/getnamo/GlobalEventSystem-Unreal/releases)
[![Github All Releases](https://img.shields.io/github/downloads/getnamo/GlobalEventSystem-Unreal/total.svg)](https://github.com/getnamo/GlobalEventSystem-Unreal/releases)

Because the events are emitted to a dynamic map of listeners you can loosely link parts of your project without needing to redo boilerplate when you change parts of the code, dynamically change environments, or e.g. load a different submap. Fire something away, and if something is interested in that information, they can do something with it; optional.

Questions? See https://github.com/getnamo/GlobalEventSystem-Unreal/issues

Discussions? See [Unreal Thread](https://forums.unrealengine.com/t/plugin-global-event-system/134063)

[Discord Server](https://discord.gg/qfJUyxaW4s)

### Current Important Issue

Emitting a struct from C++ to blueprint receiver will currently not fill properly. All other emit/receive pairs work. Use object wrapper as workaround until fix is found. Issue: https://github.com/getnamo/GlobalEventSystem-Unreal/issues/15

## Quick Install & Setup ##
 1. [Download Latest Release](https://github.com/getnamo/GlobalEventSystem-Unreal/releases)
 2. Create new or choose project.
 3. Browse to your project folder (typically found at Documents/Unreal Project/{Your Project Root})
 4. Copy *Plugins* folder into your Project root.
 5. Plugin should be now ready to use.

## How to use - Basics and API

There are globally available functions that you can use to emit and bind events. At this time there are two variants for emitting (no parameters and one wildcard parameter) and one for binding events to your local functions. There are also GameplayTag variants of these emitters and receivers for easy dropdown linking.

Each emit is a multi-cast to all valid bound receivers. If the parameters don't match you'll be warned in the log with fairly verbose messages while emitting to all other valid targets. 

Consider optionally using Gameplay tagged based emitters/receivers, or extending GESReceiver components to keep messaging organized.

### Emit Event

#### ```GESEmitEvent```

##### Param: Pinned
Whether the event should trigger for listeners added after the event has fired. Useful to communicate state.

##### Param: Domain
A string type similar to a namespace with reverse-DNS like structure encouraged, but not enforced. By default there is a ```global.default``` prefilled which can be changed to any valid utf8 string.

##### Param: Event
This is an abstract name and is considered unique for that domain. You'll need the same domain and event name to match in your binding function to receive the event.

#### ```GESEmitEventOneParam```

##### Additional Param: Parameter Data
Wildcard Property, will accept any single property type e.g. *int, float, byte, string, name, bool, struct,* and *object*. Wrap arrays and maps in a custom struct to emit more complex data types. 

Break pin to set a new type of value. 

Keep in mind that the receiving listeners need to match the property type to receive the data.

![emit](https://i.imgur.com/8nXb5ya.png)

#### ```GESEmitTagEvent```

GameplayTag variant of GESEmitEvent. Instead of ```Domain``` and ```Event``` string you pick an event from a GameplayTag via dropdown

##### Param: Domained Event Tag

A GameplayTag similar to a namespace with reverse-DNS like structure. Select or make one from the drop down list. Any depth tag should be supported and will automatically translate to domain and event under the hood.

##### Param: Pinned
Whether the event should trigger for listeners added after the event has fired. Useful to communicate state.

![image](https://user-images.githubusercontent.com/542365/113543315-f605a780-959a-11eb-9b70-83208ad2f002.png)


#### ```GESEmitTagEventOneParam```

##### Additional Param: Parameter Data
Wildcard Property, will accept any single property type e.g. *int, float, byte, string, name, bool, struct,* and *object*. Wrap arrays and maps in a custom struct to emit more complex data types. 

Break pin to set a new type of value. 

Keep in mind that the receiving listeners need to match the property type to receive the data.

![image](https://user-images.githubusercontent.com/542365/113543142-a030ff80-959a-11eb-93bd-7e9d5ec55a79.png)


### Bind Event

```GESBindEvent```
##### Param: Domain
A string type similar to a namespace with reverse-DNS like structure encouraged, but not enforced. By default there is a ```global.default``` prefilled which can be changed to any valid utf8 string.

##### Param: Event
This is an abstract name and is considered unique for that domain. You'll need the same domain and event name to match in your emitting function to receive the event.

##### Param: Receiving Function
The final parameter is your local function name. This will be called on the graph context object (owner of graph e.g. the calling actor).

![bind](https://i.imgur.com/WzHhEeG.png)

Then make your custom event or blueprint function with a matching name and matching parameters.

### Bind Event to Wildcard Delegate

Instead of linking via function name, you can connect or make a wildcard property delegate (c++ type _FGESOnePropertySignature_).

![wildcard delegate](https://i.imgur.com/bOX2lve.png)

You can then convert your received wildcard property to a fixed type with a boolean indicator if the conversion was successful. Below are the available conversion types.

![other conversions](https://i.imgur.com/iOtaJTq.png)

NB: The struct property in the conversion node will appear gray until linked with a local/member variable via e.g. a Set call.


### Bind Event via GameplayTag

Similar to the emit ```GESEmitTagEvent```, you can use the GameplayTag based variants to bind to a delegate or function by name

![image](https://user-images.githubusercontent.com/542365/113543759-e6d32980-959b-11eb-8839-97138b49c1de.png)


## Unbinding

Events automatically unbind on world end, but if you expect your receiver to last shorter than the world, consider unbinding all events attached to receiver on its _EndPlay_ call

![unbind all](https://i.imgur.com/ePryxZ4.png)

or optionally unbind individual events

![unbind](https://i.imgur.com/Qw3znMg.png)

## Examples

Keep in mind that you can start using GES incrementally for specific tasks or parts of large projects instead of replacing too many parts at once. Below are some very basic examples where GES could be useful.

### Cross-map reference pinning
Let's say you had two actors in two different sub-maps and you wanted one actor to know that it has spawned from e.g. some dynamic process. Delay nodes shown below are only used to show example event delays due to e.g. async processing or waiting on something else to happen; not needed for function.

![actor ready](https://i.imgur.com/BLUFoFs.png)

In the spawned actor you could emit a reference to itself.

![listen actor](https://i.imgur.com/IP0XTtC.png)

and in the other actor you could bind to that event to do something with that information. Normally even without pinning this event should be received because you bind before you emit. But what if you couldn't control the delay?

![delayed bind](https://i.imgur.com/UfQYsJa.png)

This is the case where pinning the event would help as now when the receiving actor binds to the event, it will automatically receive the last emit even though it was called after the event was emitted. From a developer perspective you can now just handle the receiving logic and not worry about whether you need to add delays or loop through all actors in the map. By arranging your events to signal selectively and muxing those states you can ensure that the order of your events remains predictable; only start x when part y and z in the map have happened.

### Flow muxing and loose coupling

You can add a simple actor to the map which listens to various GES events. When for example two of those events have fired you can fire off another event which is a composite logic of the source events e.g. ANDGate or much more complex logic if we decide to use variable state.

![](https://i.imgur.com/ickckJe.png)

Blueprints which would listen to the SAReady event, don't even have to care where the source came from and you could easily swap out this logic actor for maybe another type without changing any other code; an example of the loose coupling enabled by GES. The actor is replaceable, there is no additional boilerplate that needs to be changed if replaced.

## Component Receivers - Optional way of organizing events

If your receiver is an actor, you can organize your events via _GESBaseReceiverComponent_ sub-classed _ActorComponent_ receivers. These receivers automatically store the last received value and auto-unbind on EndPlay.

There are a few built-in types available e.g. a float receiver

![float receiver](https://i.imgur.com/eVCxucx.png)

Just add the component to your actor and add the OnFloatReceived Event. Change the BindSettings to match your expected _Domain_ and _Event_ names. Leave receiving function unless you want to specialize this receiver.

Below are the available built-in receivers.

![convenience receivers](https://i.imgur.com/wcECuDo.png)

#### Customizing your own receiver

Start with adding a new blueprint with _GESBaseReceiverComponent_ base class

![subclass](https://i.imgur.com/W2qvQR8.png)

Then modify your blueprint to store your own data type and forward the GES event to your own Event Dispatcher.

![example customization](https://i.imgur.com/x04WW5c.png)

e.g. a custom struct specialized receiver

You can then just add this component to all the actors that are interested in this type of event.

## Options

There are some simple options to toggle some log messages and detailed struct type checking.

![options](https://i.imgur.com/22tC4lI.png)

## C++

To use GES in C++, add ```"GlobalEventSystem"``` to your project Build.cs e.g.

```PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "GlobalEventSystem" });```

then in your implementation file of choice add the ```#include "GESHandler.h"``` header.

### Emit an event

Use _FGESHandler_ class to get static access to a default handler.

```FGESHandler::DefaultHandler()```

Call functions on this handler to both emit and bind events.

#### No param
To emit a no-param event you specify an _FGESEmitContext_ struct as the first function parameter

```c++
//define emit contexts
FGESEmitContext Context;
Context.Domain = TEXT("global.default");
Context.Event = TEXT("MyEvent");
Context.bPinned = true;      //whether the event state should be available after emit
Context.WorldContext = this; //all GES events require a WorldContext object, typically this will be an actor or anything with a world.

FGESHandler::DefaultHandler()->EmitEvent(Context);
```

#### One param

For any other emit type with one parameter, you pass the parameter value of choice as the second function parameter.
Most common types are overloaded in _EmitEvent_. For multi-param and complex data types wrap or use a UStruct or UObject sub-class.

##### FString

```c++
...

FString MyString = TEXT("MyStringData");
FGESHandler::DefaultHandler()->EmitEvent(Context, MyString);
```

or you can emit string literals via

```c++
...
FGESHandler::DefaultHandler()->EmitEvent(Context, TEXT("MyStringData"));
```

##### int32
```c++
...

FGESHandler::DefaultHandler()->EmitEvent(Context, 5);
```

##### float
```c++
...

FGESHandler::DefaultHandler()->EmitEvent(Context, 1.3);
```

##### bool

```c++
...

FGESHandler::DefaultHandler()->EmitEvent(Context, true);
```

##### FName

```c++
...

FName MyName = TEXT("my name");
FGESHandler::DefaultHandler()->EmitEvent(Context, MyName);
```

##### UObject*
```c++
...

UObject* SomeObject;

FGESHandler::DefaultHandler()->EmitEvent(Context, SomeObject);
```

##### Struct

```c++

//Assuming e.g. this custom struct definition
//NB : blueprint type declaration is optional, but will expose it to bp for easier receiving in that context
USTRUCT(BlueprintType)
struct FCustomTestData
{
    GENERATED_BODY()

    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category= Test)
    FString Name;

    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Test)
    int32 Index;

    UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Test)
    TArray<float> Data;
};

...

FCustomTestData EmitStruct;
EmitStruct.Name = TEXT("Testy");
EmitStruct.Index = 5;
EmitStruct.Data = {1.2, 2.3};


FGESHandler::DefaultHandler()->EmitEvent(FCustomTestData::StaticStruct(), &EmitStruct);
```

NB: v0.7.0 has a bug where c++ struct emits to blueprint receivers do not properly fill. Use object wrappers until a fix is found.

### Receive an event

The recommended method is using lambda receivers. Define an _FGESEventContext_ struct as the first param, then pass your overloaded lambda as the second type. NB: you can also alternatively organize your receivers with e.g. subclassing a _GESBaseReceiverComponent_, but these are only applicable for actor owners and thus not recommended over lambda receivers in general. 

#### No param event

Only the event context is required. Use 'this' capture context in the lambda to enable calling e.g. member functions (optional).

```c++
FGESEventContext Context;
Context.Domain = TEXT("global.default");
Context.Event = TEXT("MyEvent");
Context.WorldContext = this;
 
FGESHandler::DefaultHandler()->AddLambdaListener(Context, [this]
{
    //handle receive
});
```

#### FString param event

```c++
...

FGESHandler::DefaultHandler()->AddLambdaListener(Context, [this](const FString& StringData)
{
    //handle receive, e.g. log result
    UE_LOG(LogTemp, Log, TEXT("Received %s"), *StringData);
});
```

#### float param event

```c++
...

FGESHandler::DefaultHandler()->AddLambdaListener(Context, [this](float FloatData)
{
    //handle receive, e.g. log result
    UE_LOG(LogTemp, Log, TEXT("Received %1.3f"), FloatData);
});
```

#### int32 param event

NB: name specialization of this bind due to lambda bind ambiguity with float callback

```c++
...

FGESHandler::DefaultHandler()->AddLambdaListenerInt(Context, [this](int32 IntData)
{
    //handle receive, e.g. log result
    UE_LOG(LogTemp, Log, TEXT("Received %d"), IntData);
});
```

#### bool param event

NB: name specialization of this bind due to lambda bind ambiguity with float callback

```c++
...

FGESHandler::DefaultHandler()->AddLambdaListenerBool(Context, [this](bool BoolData)
{
    //handle receive, e.g. log result
    UE_LOG(LogTemp, Log, TEXT("Received %d"), BoolData);
});
```

#### FName param event

```c++
...

FGESHandler::DefaultHandler()->AddLambdaListener(Context, [this](const FName& NameData)
{
    //handle receive, e.g. log result
    UE_LOG(LogTemp, Log, TEXT("Received %s"), *NameData.ToString());
});
```

#### UObject* param event

```c++
...

FGESHandler::DefaultHandler()->AddLambdaListener(Context, [this](UObject* ObjectData)
{
    //handle receive, e.g. log result
    UE_LOG(LogTemp, Log, TEXT("Received %s"), *ObjectData.GetName());
});
```

#### Struct param event

Structs need a deep copy to be readable.

```c++

//Assuming this custom struct
USTRUCT(BlueprintType)
struct FCustomTestData
{
	GENERATED_BODY()

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category=Test)
	FString Name;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Test)
	int32 Index;

	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Test)
	TArray<float> Data;
};

...

FGESHandler::DefaultHandler()->AddLambdaListener(Context, [this](UStruct* Struct, void* StructPtr)
{
    //Confirm matching struct
    if (Struct == FCustomTestData::StaticStruct())
    {
        //Deep copy your struct to local ref
	FCustomTestData TestData;
	TestData = *(FCustomTestData*)StructPtr;
        
	//Test data is now usable
	UE_LOG(LogTemp, Log, TEXT("Struct data: %s %d"), *TestData.Name, TestData.Data.Num());
    }
});
```

#### Wildcard
If you're not sure of the type of data you can receive, try a wildcard lambda and cast to test validity of data types. You'll need to add ```"#include "GlobalEventSystemBPLibrary.h"``` to use the wildcard property conversion functions.

```c++
...
FGESHandler::DefaultHandler()->AddLambdaListener(Context, [this](const FGESWildcardProperty& WildcardProperty)
{
    //Let's try to decode a float
    float MaybeFloat;
    bool bDidGetFloat = UGlobalEventSystemBPLibrary::Conv_PropToFloat(WildcardProperty, MaybeFloat);
    if(bDidGetFloat)
    {
        //good to go
    }
});
```

#### Unbinding Events
Each bound event function should unbind automatically when the world gets removed, but it is recommended to remove your listener if your receiver has a shorter lifetime e.g. on its _EndPlay_ call.

Remove all listeners attached to this owner (where _this_ == world context object).

```c++
FGESHandler::DefaultHandler()->RemoveAllListenersForReceiver(this);
```

Or you unbind each listener via the returned lambda function name you get when you bind the listener to the event.

```c++
...

//Store a reference to your lambda via string name
FString LambdaFunctionName = FGESHandler::DefaultHandler()->AddLambdaListener(Context, [this]
{
    //handle receive
});

...

//let's say we're done listening now
FGESHandler::DefaultHandler()->RemoveLambdaListener(Context, LambdaFunctionName);

```

Optionally you can also store the function and pass it instead of the lambda name to unbind it. Name method is preferred due to developers often defining anonymous functions inline when binding.

## When not to use GES
- There are some performance considerations to keep in mind. While the overall architecture is fairly optimized, it can be more expensive than a simple function call due to function and type checking. Consider it appropriate for signaling more than a hammer to use everywhere.

- If your objects have a tight coupling or it's easily accessible in a tree hierarchy pattern I would use standard methods instead of GES.

- Background threads. Current version is not thread safe and should be called only in your game thread.

## Possible Improvements
See https://github.com/getnamo/GlobalEventSystem-Unreal/issues for latest.
General enhancements:
- Event with callback (get information from a listener)
- Add optional logging utility to record event flow with possibly replay (attach middleware function)
- Trigger limits, e.g. can only trigger n times
- Add receiver limits (target requires interface/etc)
- Bind to Interface (binds all events in an interface map to functions in interface)


================================================
FILE: Source/GlobalEventSystem/GlobalEventSystem.Build.cs
================================================
// Some copyright should be here...

using UnrealBuildTool;

public class GlobalEventSystem : ModuleRules
{
	public GlobalEventSystem(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
		
		PublicIncludePaths.AddRange(
			new string[] {
				// ... add public include paths required here ...
			}
			);
				
		
		PrivateIncludePaths.AddRange(
			new string[] {
				// ... add other private include paths required here ...
			}
			);
			
		
		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",
				// ... add other public dependencies that you statically link with here ...
			}
			);
			
		
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"CoreUObject",
				"Engine",
				"Slate",
				"SlateCore",
				"GameplayTags"
				// ... add private dependencies that you statically link with here ...	
			}
			);
		
		
		DynamicallyLoadedModuleNames.AddRange(
			new string[]
			{
				// ... add any modules that your module loads dynamically here ...
			}
			);
		
		if (Target.Type == TargetRules.TargetType.Editor)
		{
			PublicDependencyModuleNames.Add("UnrealEd");
		} 
	}
}


================================================
FILE: Source/GlobalEventSystem/Private/GESBaseReceiverComponent.cpp
================================================
#include "GESBaseReceiverComponent.h"
#include "GlobalEventSystemBPLibrary.h"

UGESBaseReceiverComponent::UGESBaseReceiverComponent(const FObjectInitializer& init) : UActorComponent(init)
{
	bBindOnBeginPlay = true;
	bUnbindOnEndPlay = true;
	bPinInternalDataForPolling = true;
	bDidReceiveEventAtLeastOnce = false;

	BindSettings.ReceivingFunction = TEXT("OnEvent(component)");
}

void UGESBaseReceiverComponent::BeginPlay()
{
	Super::BeginPlay();
	if (bBindOnBeginPlay)
	{
		//special case
		if (BindSettings.ReceivingFunction == TEXT("OnEvent(component)"))
		{
			InternalListener.BindDynamic(this, &UGESBaseReceiverComponent::HandleInternalEvent);
			UGlobalEventSystemBPLibrary::GESBindEventToDelegate(this, InternalListener, BindSettings.Domain, BindSettings.Event);
		}
		else
		{
			UGlobalEventSystemBPLibrary::GESBindEvent(this, BindSettings.Domain, BindSettings.Event, BindSettings.ReceivingFunction);
		}
	}
}

void UGESBaseReceiverComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	if (bUnbindOnEndPlay)
	{
		if (BindSettings.ReceivingFunction == TEXT("OnEvent(component)"))
		{
			UGlobalEventSystemBPLibrary::GESUnbindDelegate(this, InternalListener, BindSettings.Domain, BindSettings.Event);
			PinnedData.CleanupPinnedData();
		}
		else
		{
			UGlobalEventSystemBPLibrary::GESUnbindEvent(this, BindSettings.Domain, BindSettings.Event, BindSettings.ReceivingFunction);
		}
	}
	Super::EndPlay(EndPlayReason);
}

void UGESBaseReceiverComponent::HandleInternalEvent(const FGESWildcardProperty& WildcardProperty)
{
	LastReceivedProperty = WildcardProperty;

	if (bPinInternalDataForPolling)
	{
		PinnedData.Property = WildcardProperty.Property.Get();
		PinnedData.PropertyPtr = WildcardProperty.PropertyPtr;

		//We need to use pinning to catch non-pinned data emitted
		PinnedData.CopyPropertyToPinnedBuffer();

		LastReceivedProperty.Property = PinnedData.Property;
		LastReceivedProperty.PropertyPtr = PinnedData.PropertyPtr;
	}

	bDidReceiveEventAtLeastOnce = true;

	OnEvent.Broadcast(WildcardProperty);
}


================================================
FILE: Source/GlobalEventSystem/Private/GESDataTypes.cpp
================================================
#include "GESDataTypes.h"


================================================
FILE: Source/GlobalEventSystem/Private/GESHandler.cpp
================================================
#include "GESHandler.h"
#include "GlobalEventSystemBPLibrary.h"
#include "Engine/World.h"

TSharedPtr<FGESHandler> FGESHandler::PrivateDefaultHandler = MakeShareable(new FGESHandler());

void FGESHandler::Clear()
{
	PrivateDefaultHandler = MakeShareable(new FGESHandler());
}

bool FGESHandler::FirstParamIsCppType(UFunction* Function, const FString& TypeString)
{
	TArray<FProperty*> Properties;
	FunctionParameters(Function, Properties);
	if (Properties.Num() == 0)
	{
		return false;
	}

	const FString& FirstParam = Properties[0]->GetCPPType();
	return (FirstParam == TypeString);
}

bool FGESHandler::FirstParamIsSubclassOf(UFunction* Function, FFieldClass* ClassType)
{
	TArray<FProperty*> Properties;
	FunctionParameters(Function, Properties);
	if (Properties.Num() == 0)
	{
		return false;
	}
	return Properties[0]->GetClass()->IsChildOf(ClassType);
}

FString FGESHandler::ListenerLogString(const FGESEventListener& Listener)
{
	return Listener.ReceiverWCO.Get()->GetName() + TEXT(":") + Listener.FunctionName;
}

FString FGESHandler::EventLogString(const FGESEvent& Event)
{
	return Event.Domain + TEXT(".") + Event.Event;
}

FString FGESHandler::EmitEventLogString(const FGESEmitContext& EmitData)
{
	return EmitData.Domain + TEXT(".") + EmitData.Event;
}

void FGESHandler::FunctionParameters(UFunction* Function, TArray<FProperty*>& OutParamProperties)
{
	TFieldIterator<FProperty> Iterator(Function);

	while (Iterator && (Iterator->PropertyFlags & CPF_Parm))
	{
		FProperty* Prop = *Iterator;
		OutParamProperties.Add(Prop);
		++Iterator;
	}
}

bool FGESHandler::FunctionHasValidParams(UFunction* Function, FFieldClass* ClassType, const FGESEmitContext& EmitData, const FGESEventListener& Listener)
{
	if (FirstParamIsSubclassOf(Function, ClassType))
	{
		return true;
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("FGESHandler::EmitEvent %s skipped listener %s due to function not having a matching %s signature."),
			*EmitEventLogString(EmitData),
			*ListenerLogString(Listener),
			*ClassType->GetName());
		return false;
	}
}

TSharedPtr<FGESHandler> FGESHandler::DefaultHandler()
{
	return FGESHandler::PrivateDefaultHandler;
}

void FGESHandler::CreateEvent(const FString& Domain, const FString& Event, bool bPinned /*= false*/)
{
	FGESEvent CreatedFunction;
	CreatedFunction.Domain = Domain;
	CreatedFunction.Event = Event;
	CreatedFunction.bPinned = bPinned;
	EventMap.Add(Key(Domain, Event), CreatedFunction);
}

void FGESHandler::DeleteEvent(const FString& Domain, const FString& Event)
{
	DeleteEvent(Key(Domain, Event));
}

void FGESHandler::DeleteEvent(const FString& DomainAndEvent)
{
	//ensure any pinned data gets cleaned up on event deletion
	if (EventMap.Contains(DomainAndEvent))
	{
		FGESEvent& Event = EventMap[DomainAndEvent];
		if (Event.bPinned)
		{
			Event.PinnedData.CleanupPinnedData();
		}
	}

	//remove the event
	EventMap.Remove(DomainAndEvent);
}

bool FGESHandler::HasEvent(const FString& Domain, const FString& Event)
{
	return EventMap.Contains(Key(Domain, Event));
}

void FGESHandler::UnpinEvent(const FString& Domain, const FString& EventName)
{
	FString KeyString = Key(Domain, EventName);
	if (EventMap.Contains(KeyString))
	{
		FGESEvent& Event = EventMap[KeyString];
		Event.bPinned = false;
		//Event.PinnedData.Property->RemoveFromRoot();
		//Event.PinnedData.PropertyData.Empty();  not sure if safe to delete instead of rebuilding on next pin
	}
}

void FGESHandler::AddListener(const FString& Domain, const FString& EventName, const FGESEventListener& Listener)
{
	FString KeyString = Key(Domain, EventName);

	//Create event if not already created
	if (!EventMap.Contains(KeyString))
	{
		CreateEvent(Domain, EventName);
	}

	//Check passed listener validity
	if (Listener.IsValidListener())
	{
		//Actually add this valid listener to map
		FGESEvent& Event = EventMap[KeyString];
		Event.Listeners.Add(Listener);

		//TODO: check receivermap logic
		FGESEventListenerWithContext ListenContext;
		ListenContext.Domain = Domain;
		ListenContext.Event = EventName;
		FGESMinimalEventListener Minimal;
		Minimal.FunctionName = Listener.FunctionName;
		Minimal.ReceiverWCO = Listener.ReceiverWCO;
		ListenContext.Listener = Minimal;

		if (!ReceiverMap.Contains(Listener.ReceiverWCO.Get()))
		{
			TArray<FGESEventListenerWithContext> Array;
			ReceiverMap.Add(Listener.ReceiverWCO.Get(), Array);
		}
		ReceiverMap[Listener.ReceiverWCO.Get()].Add(ListenContext);

		//if it's pinned re-emit it immediately to this listener
		if (Event.bPinned) 
		{
			FGESPropertyEmitContext EmitData;
			
			EmitData.Domain = Domain;
			EmitData.Event = EventName;

			EmitData.Property = Event.PinnedData.Property;
			EmitData.PropertyPtr = Event.PinnedData.PropertyPtr;
			EmitData.bPinned = Event.bPinned;
			EmitData.SpecificTarget = (FGESEventListener*)&Listener;	//this immediate call should only be calling our listener
			EmitData.WorldContext = Event.WorldContext;
			
			//did we fail to emit?
			if (!EmitPropertyEvent(EmitData))
			{
				//did the event get removed due to being stale? The listener may still be valid so re-run this add listener loop
				if (!HasEvent(Domain, EventName))
				{
					AddListener(Domain, EventName, Listener);
				}
			}
		}
	}
	else
	{
		//NB: validity can be violated due to delegate and lambda too
		//TODO: add warnings in case of invalid delegate/lambda function binds

		//Not valid, emit warnings
		if (Listener.ReceiverWCO->IsValidLowLevelFast())
		{
			UE_LOG(LogTemp, Warning, TEXT("FGESHandler::AddListener Warning: \n%s does not have the function '%s'. Attempted to bind to GESEvent %s.%s"), *Listener.ReceiverWCO->GetFullName(), *Listener.FunctionName, *Domain, *EventName);
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("FGESHandler::AddListener: (invalid object) does not have the function '%s'. Attempted to bind to GESEvent %s.%s"), *Listener.FunctionName, *Domain, *EventName);
		}
	}
}

FString FGESHandler::AddLambdaListener(FGESEventContext Context, TFunction<void(const FGESWildcardProperty&)> ReceivingLambda)
{
	if (Context.WorldContext == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("FGESHandler::AddLambdaListener No valid world context provided. Not added."));
		return TEXT("Invalid");
	}
	FGESEventListener Listener;
	Listener.bIsBoundToLambda = true;
	Listener.LambdaFunction = ReceivingLambda;
	Listener.ReceiverWCO = Context.WorldContext;

	//name is derived from WCO + lambda pointer address
	FString FunctionPtr = FString::Printf(TEXT("%d"), (void*)&ReceivingLambda);
	Listener.FunctionName = Listener.ReceiverWCO->GetName() + TEXT(".lambda.") + FunctionPtr;

	AddListener(Context.Domain, Context.Event, Listener);

	return Listener.FunctionName;
}

FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunction<void(UStruct* Struct, void* StructPtr)> ReceivingLambda)
{
	return AddLambdaListener(BindInfo,
		[ReceivingLambda](const FGESWildcardProperty& Data)
		{
			FStructProperty* StructProperty = CastField<FStructProperty>(Data.Property.Get());

			if (!StructProperty)
			{
				UE_LOG(LogTemp, Warning, TEXT("FGESHandler::AddLambdaListener callback: Expected a property structure, received %s; Receive skipped."), *Data.Property->GetName());
				return;
			}

			ReceivingLambda(StructProperty->Struct, Data.PropertyPtr);
		});
}

FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunction<void(const FString&)> ReceivingLambda)
{
	return AddLambdaListener(BindInfo,
		[ReceivingLambda](const FGESWildcardProperty& Data)
		{
			FString Value;
			UGlobalEventSystemBPLibrary::Conv_PropToStringRef(Data, Value);
			ReceivingLambda(Value);
		});
}

FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunction<void(UObject*)> ReceivingLambda)
{
	return AddLambdaListener(BindInfo,
		[ReceivingLambda](const FGESWildcardProperty& Data)
		{
			UObject* Value = 0;
			UGlobalEventSystemBPLibrary::Conv_PropToObject(Data, Value);
			ReceivingLambda(Value);
		});
}

FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunction<void(float)> ReceivingLambda)
{
	return AddLambdaListener(BindInfo,
		[ReceivingLambda](const FGESWildcardProperty& Data)
		{
			float Value = 0;
			UGlobalEventSystemBPLibrary::Conv_PropToFloat(Data, Value);
			ReceivingLambda(Value);
		});
}

FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunction<void(const FName&)> ReceivingLambda)
{
	return AddLambdaListener(BindInfo,
		[ReceivingLambda](const FGESWildcardProperty& Data)
		{
			FName Value;
			UGlobalEventSystemBPLibrary::Conv_PropToName(Data, Value);
			ReceivingLambda(Value);
		});
}

FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunction<void(void)> ReceivingLambda)
{
	return AddLambdaListener(BindInfo,
		[ReceivingLambda](const FGESWildcardProperty& Data)
		{
			ReceivingLambda();
		});
}

FString FGESHandler::AddLambdaListenerInt(FGESEventContext EventInfo, TFunction<void(int32)> ReceivingLambda)
{
	return AddLambdaListener(EventInfo,
		[ReceivingLambda](const FGESWildcardProperty& Data)
		{
			int32 Value = 0;
			UGlobalEventSystemBPLibrary::Conv_PropToInt(Data, Value);

			ReceivingLambda(Value);
		});
}

FString FGESHandler::AddLambdaListenerBool(FGESEventContext EventInfo, TFunction<void(bool)> ReceivingLambda)
{
	return AddLambdaListener(EventInfo,
		[ReceivingLambda](const FGESWildcardProperty& Data)
		{
			bool Value = false;
			UGlobalEventSystemBPLibrary::Conv_PropToBool(Data, Value);
			ReceivingLambda(Value);
		});
}

void FGESHandler::RemoveListener(const FString& Domain, const FString& Event, const FGESEventListener& Listener)
{
	FString KeyString = Key(Domain, Event);
	if (!EventMap.Contains(KeyString))
	{
		if (Options.bLogStaleRemovals)
		{
			UE_LOG(LogTemp, Warning, TEXT("FGESHandler::RemoveListener, tried to remove a listener from an event that doesn't exist (%s.%s). Ignored."), *Domain, *Event);
		}
		return;
	}

	//Remove from main listener map
	EventMap[KeyString].Listeners.Remove(Listener);

	//Remove matched entry in receiver map
	if (ReceiverMap.Contains(Listener.ReceiverWCO.Get()))
	{
		FGESEventListenerWithContext ContextListener;
		ContextListener.Domain = Domain;
		ContextListener.Event = Event;
		ContextListener.Listener.FunctionName = Listener.FunctionName;
		ContextListener.Listener.ReceiverWCO = Listener.ReceiverWCO;
		ReceiverMap[Listener.ReceiverWCO.Get()].Remove(ContextListener);
	}
}

void FGESHandler::RemoveAllListenersForReceiver(UObject* ReceiverWCO)
{
	if (!ReceiverMap.Contains(ReceiverWCO))
	{
		UE_LOG(LogTemp, Warning, TEXT("FGESHandler::RemoveAllListenersForReceiver, tried to remove listeners from an WCO that doesn't exist. Ignored."));
		return;
	}

	//Copy array so we can loop over
	TArray<FGESEventListenerWithContext> ReceiverArray = ReceiverMap[ReceiverWCO];
	
	for (FGESEventListenerWithContext& ListenContext: ReceiverArray)
	{
		RemoveListener(ListenContext.Domain, ListenContext.Event, FGESEventListener(ListenContext.Listener));
	}

	ReceiverMap.Remove(ReceiverWCO);
}

void FGESHandler::RemoveLambdaListener(FGESEventContext BindInfo, TFunction<void(const FGESWildcardProperty&)> ReceivingLambda)
{
	FGESEventListener Listener;
	Listener.bIsBoundToLambda = true;
	Listener.LambdaFunction = ReceivingLambda;
	Listener.ReceiverWCO = BindInfo.WorldContext;

	FString FunctionPtr = FString::Printf(TEXT("%d"), (void*)&ReceivingLambda);
	Listener.FunctionName = Listener.ReceiverWCO->GetName() + TEXT(".lambda.") + FunctionPtr;

	RemoveListener(BindInfo.Domain, BindInfo.Event, Listener);
}

void FGESHandler::RemoveLambdaListener(FGESEventContext BindInfo, const FString& LambdaName)
{
	FGESEventListener Listener;
	Listener.bIsBoundToLambda = true;
	Listener.ReceiverWCO = BindInfo.WorldContext;
	Listener.FunctionName = LambdaName;

	RemoveListener(BindInfo.Domain, BindInfo.Event, Listener);
}

void FGESHandler::EmitToListenersWithData(const FGESPropertyEmitContext& EmitData, TFunction<void(const FGESEventListener&)> DataFillCallback)
{
	FString KeyString = Key(EmitData.Domain, EmitData.Event);
	if (!EventMap.Contains(KeyString))
	{
		CreateEvent(EmitData.Domain, EmitData.Event, false);
	}
	FGESEvent& Event = EventMap[KeyString];
	Event.WorldContext = EmitData.WorldContext;

	if (EmitData.WorldContext == nullptr)
	{
		UE_LOG(LogTemp, Error, TEXT("FGESHandler::EmitToListenersWithData: Emitted event has no world context!"));
		return;
	}

	UWorld* World = EmitData.WorldContext->GetWorld();
	if (!World->IsValidLowLevelFast())
	{
		UE_LOG(LogTemp, Error, TEXT("FGESHandler::EmitToListenersWithData: Emitted event has no world!"));
		return;
	}

	//Attach a world listener to each unique world
	if (!WorldMap.Contains(World))
	{
		AGESWorldListenerActor* WorldListener = World->SpawnActor<AGESWorldListenerActor>();
		WorldListener->OnEndPlay = [this, WorldListener, World]
		{
			for (const FString& EventKey : WorldListener->WorldEvents)
			{
				DeleteEvent(EventKey);
			}
			WorldListener->WorldEvents.Empty();

			//For now always clear receiver map if any world ends
			ReceiverMap.Empty();

			WorldMap.Remove(World);
		};
		WorldMap.Add(World, WorldListener);
	}

	//ensure this event is registered
	WorldMap[World]->WorldEvents.Add(KeyString);

	//is there a property to pin?
	if (EmitData.Property)
	{
		//Warn if we're trying to pin a new event without unpinning old one
		if (Event.bPinned && EmitData.bPinned)
		{
			//cleanup if different
			if (EmitData.Property != Event.PinnedData.Property ||
				EmitData.PropertyPtr != Event.PinnedData.PropertyPtr)
			{
				Event.PinnedData.CleanupPinnedData();
			}
			
			Event.PinnedData.bHandlePropertyDeletion = EmitData.bHandleAllocation;
			Event.PinnedData.Property = EmitData.Property;

			//only copy if ptrs are different or nullptr
			if (EmitData.PropertyPtr != Event.PinnedData.PropertyPtr || 
				Event.PinnedData.PropertyPtr == nullptr)
			{
				Event.PinnedData.PropertyPtr = EmitData.PropertyPtr;
				Event.PinnedData.CopyPropertyToPinnedBuffer();
			}
		
			//UE_LOG(LogTemp, Warning, TEXT("FGESHandler::EmitToListenersWithData Emitted a pinned event to an already pinned event. Pinned data updated."));
		}
		if (!Event.bPinned && EmitData.bPinned)
		{
			Event.PinnedData.CleanupPinnedData();
			Event.PinnedData.bHandlePropertyDeletion = EmitData.bHandleAllocation;
			Event.PinnedData.Property = EmitData.Property;
			Event.PinnedData.PropertyPtr = EmitData.PropertyPtr;
			Event.PinnedData.CopyPropertyToPinnedBuffer();
		}
	}
	Event.bPinned = EmitData.bPinned;


	//only emit to this target
	if (EmitData.SpecificTarget)
	{
		FGESEventListener Listener = *EmitData.SpecificTarget;

		//stale listener, remove it
		if (!Listener.ReceiverWCO->IsValidLowLevelFast())
		{
			RemovalArray.Add(&Listener);
		}
		else
		{
			//potential issue: this opt bypasses specialization via datafillcallback
			EmitToListenerWithData(EmitData, Listener, DataFillCallback);
		}
	}
	//emit to all targets
	else
	{
		for (FGESEventListener& Listener : Event.Listeners)
		{
			//stale listener, remove it
			if (!Listener.ReceiverWCO->IsValidLowLevelFast())
			{
				RemovalArray.Add(&Listener);
			}
			else
			{
				//potential issue: this opt bypasses specialization via datafillcallback
				EmitToListenerWithData(EmitData, Listener, DataFillCallback);
			}
		}
	}

	//Go through stale listeners and remove them
	if (RemovalArray.Num() > 0)
	{
		for (int i = 0; i < RemovalArray.Num(); i++)
		{
			FGESEventListener Listener = *RemovalArray[i];
			Event.Listeners.Remove(Listener);
		}
		if (Options.bLogStaleRemovals)
		{
			UE_LOG(LogTemp, Log, TEXT("FGESHandler::EmitEvent: auto-removed %d stale listeners."), RemovalArray.Num());
		}
		RemovalArray.Empty();
	}
}

bool FGESHandler::EmitToListenerWithData(const FGESPropertyEmitContext& EmitData, const FGESEventListener& Listener, TFunction<void(const FGESEventListener&)>& DataFillCallback)
{
	if (Listener.ReceiverWCO->IsValidLowLevelFast())
	{
		if (Listener.bIsBoundToLambda && Listener.LambdaFunction != nullptr)
		{
			//Opt1) this listener is handled by lambda
			FGESWildcardProperty Wrapper;
			Wrapper.Property = EmitData.Property;
			Wrapper.PropertyPtr = EmitData.PropertyPtr;

			Listener.LambdaFunction(Wrapper);
			return true;
		}
		if (Listener.bIsBoundToDelegate)
		{
			//Opt2) this listener is handled by wildcard event delegate
			FGESWildcardProperty Wrapper;
			Wrapper.Property = EmitData.Property;
			Wrapper.PropertyPtr = EmitData.PropertyPtr;
			Listener.OnePropertyFunctionDelegate.ExecuteIfBound(Wrapper);
			return true;
		}

		UFunction* BPFunction = Listener.ReceiverWCO->FindFunction(FName(*Listener.FunctionName));
		if (BPFunction != nullptr)
		{
			//Opt3) listener is handled by function bind by name
			DataFillCallback(Listener);
			return true;
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("FGESHandler::EmitEvent: Function not found '%s'"), *Listener.FunctionName);
			return false;
		}
	}
	return false;
}

void FGESHandler::EmitEvent(const FGESEmitContext& EmitData, UStruct* Struct, void* StructPtr)
{
	bool bValidateStructs = Options.bValidateStructTypes;
	FGESPropertyEmitContext PropData(EmitData);
	UClass* Class = EmitData.WorldContext->GetClass();

	FField* OldProperty = Class->ChildProperties;

	FStructProperty* StructProperty = new FStructProperty(FFieldVariant(Class), TEXT("StructProperty"), RF_NoFlags);
	StructProperty->Struct = (UScriptStruct*)Struct;
	StructProperty->ElementSize = Struct->GetStructureSize();

	//undo what we just did so it won't be traversed because of init
	Class->ChildProperties = OldProperty;

	//Store our struct data in a buffer we can reference
	TArray<uint8> Buffer;
	int32 Size = Struct->GetStructureSize();
	Buffer.SetNum(Size);

	//StructProperty->CopyCompleteValue(Buffer.GetData(), StructPtr);
	FPlatformMemory::Memcpy(Buffer.GetData(), StructPtr, Size);

	PropData.Property = StructProperty;
	PropData.PropertyPtr = Buffer.GetData();
	if (PropData.bPinned)
	{
		PropData.bHandleAllocation = true;
	}

	EmitToListenersWithData(PropData, [&PropData, &Struct, &Buffer, bValidateStructs](const FGESEventListener& Listener)
	{
		UE_LOG(LogTemp, Warning, TEXT("FGESHandler::EmitEvent struct Emit called"));

		if (FunctionHasValidParams(Listener.Function, FStructProperty::StaticClass(), PropData, Listener))
		{
			if (bValidateStructs)
			{
				UE_LOG(LogTemp, Warning, TEXT("Validation emit"));

				//For structs we can have different mismatching structs at this point check class types
				//optimization note: unroll the above function for structs to avoid double param lookup
				TArray<FProperty*> Properties;
				FunctionParameters(Listener.Function, Properties);
				FStructProperty* SubStructProperty = CastField<FStructProperty>(Properties[0]);
				if (SubStructProperty->Struct == Struct)
				{
					Listener.ReceiverWCO->ProcessEvent(Listener.Function, PropData.PropertyPtr);
				}
				else
				{
					UE_LOG(LogTemp, Warning, TEXT("FGESHandler::EmitEvent %s skipped listener %s due to function not having a matching Struct type %s signature."),
						*EmitEventLogString(PropData),
						*ListenerLogString(Listener),
						*Struct->GetName());
				}
			}
			//No validation, e.g. vector-> rotator fill is accepted
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("No validation emit"));
				Listener.ReceiverWCO->ProcessEvent(Listener.Function, (void*)Buffer.GetData()); //PropData.PropertyPtr); //
			}
		}
	});


	if (!EmitData.bPinned)
	{
		delete StructProperty;
	}
}

void FGESHandler::EmitEvent(const FGESEmitContext& EmitData, const FString& ParamData)
{
	FGESPropertyEmitContext PropData(EmitData);

	//We have no property context, make a new property
	FStrProperty* StrProperty = 
		new FStrProperty(FFieldVariant(EmitData.WorldContext->GetClass()),
			TEXT("StringValue"),
			EObjectFlags::RF_Public | EObjectFlags::RF_LoadCompleted);

	//Wrap our FString into a buffer we can share
	TArray<uint8> Buffer;
	Buffer.SetNum(ParamData.GetAllocatedSize());

	StrProperty->SetPropertyValue_InContainer(Buffer.GetData(), ParamData);

	PropData.Property = StrProperty;
	PropData.PropertyPtr = Buffer.GetData();
	if (PropData.bPinned)
	{
		PropData.bHandleAllocation = true;
	}

	EmitToListenersWithData(PropData, [&PropData, ParamData](const FGESEventListener& Listener)
	{
		if (FunctionHasValidParams(Listener.Function, FStrProperty::StaticClass(), PropData, Listener))
		{
			Listener.ReceiverWCO->ProcessEvent(Listener.Function, PropData.PropertyPtr);// (void*)*MutableString); // (void*)&ParamData);
		}
	});

	if (!EmitData.bPinned)
	{
		delete StrProperty;
	}
}

void FGESHandler::EmitEvent(const FGESEmitContext& EmitData, UObject* ParamData)
{
	FGESPropertyEmitContext PropData(EmitData);

	FObjectProperty* ObjectProperty =
		new FObjectProperty(FFieldVariant(EmitData.WorldContext->GetClass()),
			TEXT("ObjectValue"),
			EObjectFlags::RF_Public | EObjectFlags::RF_LoadCompleted);

	//wrapper required to avoid copied pointer to become the first function
	FGESDynamicArg ParamWrapper;
	ParamWrapper.Arg01 = ParamData;

	PropData.Property = ObjectProperty;
	PropData.PropertyPtr = (void*)&ParamWrapper;
	
	if (PropData.bPinned)
	{
		PropData.bHandleAllocation = true;
	}

	EmitToListenersWithData(PropData, [&PropData, ParamWrapper](const FGESEventListener& Listener)
	{
		if (FunctionHasValidParams(Listener.Function, FObjectProperty::StaticClass(), PropData, Listener))
		{
			Listener.ReceiverWCO->ProcessEvent(Listener.Function, (void*)&ParamWrapper);// PropData.PropertyPtr);
		}
	});

	if (!EmitData.bPinned)
	{
		delete ObjectProperty;
	}
}

void FGESHandler::EmitEvent(const FGESEmitContext& EmitData, float ParamData)
{
	FGESPropertyEmitContext PropData(EmitData);

	FGESWildcardProperty WrapperProperty;

	FFloatProperty* FloatProperty =
		new FFloatProperty(FFieldVariant(EmitData.WorldContext->GetClass()),//WrapperProperty),
			TEXT("FloatValue"),
			EObjectFlags::RF_Public | EObjectFlags::RF_LoadCompleted);

	PropData.Property = FloatProperty;
	PropData.PropertyPtr = &ParamData;// Buffer.GetData();
	if (PropData.bPinned)
	{
		PropData.bHandleAllocation = true;
	}

	EmitToListenersWithData(PropData, [&PropData, &ParamData](const FGESEventListener& Listener)
	{
		if (FunctionHasValidParams(Listener.Function, FNumericProperty::StaticClass(), PropData, Listener))
		{
			Listener.ReceiverWCO->ProcessEvent(Listener.Function, PropData.PropertyPtr);// PropData.PropertyPtr);
		}
	});

	if (!EmitData.bPinned)
	{
		delete FloatProperty;
	}
}

void FGESHandler::EmitEvent(const FGESEmitContext& EmitData, int32 ParamData)
{
	FGESPropertyEmitContext PropData(EmitData);

	FIntProperty* IntProperty =
		new FIntProperty(FFieldVariant(EmitData.WorldContext->GetClass()),
			TEXT("IntValue"),
			EObjectFlags::RF_Public | EObjectFlags::RF_LoadCompleted);

	PropData.Property = IntProperty;
	PropData.PropertyPtr = &ParamData;
	if (PropData.bPinned)
	{
		PropData.bHandleAllocation = true;
	}

	EmitToListenersWithData(PropData, [&PropData](const FGESEventListener& Listener)
	{
		if (FunctionHasValidParams(Listener.Function, FNumericProperty::StaticClass(), PropData, Listener))
		{
			Listener.ReceiverWCO->ProcessEvent(Listener.Function, PropData.PropertyPtr);
		}
	});

	if (!EmitData.bPinned)
	{
		delete IntProperty;
	}
}

void FGESHandler::EmitEvent(const FGESEmitContext& EmitData, bool ParamData)
{
	FGESPropertyEmitContext PropData(EmitData);

	FBoolProperty* BoolProperty =
		new FBoolProperty(FFieldVariant(EmitData.WorldContext->GetClass()),
			TEXT("BoolValue"),
			EObjectFlags::RF_Public | EObjectFlags::RF_LoadCompleted);

	PropData.Property = BoolProperty;
	PropData.PropertyPtr = &ParamData;
	if (PropData.bPinned)
	{
		PropData.bHandleAllocation = true;
	}

	EmitToListenersWithData(PropData, [&PropData](const FGESEventListener& Listener)
	{
		if (FunctionHasValidParams(Listener.Function, FBoolProperty::StaticClass(), PropData, Listener))
		{
			Listener.ReceiverWCO->ProcessEvent(Listener.Function, PropData.PropertyPtr);
		}
	});

	if (!EmitData.bPinned)
	{
		delete BoolProperty;
	}
}

void FGESHandler::EmitEvent(const FGESEmitContext& EmitData, const FName& ParamData)
{
	FGESPropertyEmitContext PropData(EmitData);

	//We have no property context, make a new property
	FNameProperty* NameProperty =
		new FNameProperty(FFieldVariant(EmitData.WorldContext->GetClass()),
			TEXT("NameValue"),
			EObjectFlags::RF_Public | EObjectFlags::RF_LoadCompleted);

	//Wrap our FName into a buffer we can share
	TArray<uint8> Buffer;
	Buffer.SetNum(ParamData.StringBufferSize);

	NameProperty->SetPropertyValue_InContainer(Buffer.GetData(), ParamData);

	PropData.Property = NameProperty;
	PropData.PropertyPtr = Buffer.GetData();
	if (PropData.bPinned)
	{
		PropData.bHandleAllocation = true;
	}

	EmitToListenersWithData(PropData, [&PropData, ParamData](const FGESEventListener& Listener)
		{
			if (FunctionHasValidParams(Listener.Function, FStrProperty::StaticClass(), PropData, Listener))
			{
				Listener.ReceiverWCO->ProcessEvent(Listener.Function, PropData.PropertyPtr);
			}
		});

	if (!EmitData.bPinned)
	{
		delete NameProperty;
	}
}

bool FGESHandler::EmitEvent(const FGESEmitContext& EmitData)
{
	FGESPropertyEmitContext FullEmitData(EmitData);

	//No param version
	return EmitPropertyEvent(FullEmitData);
}

void FGESHandler::EmitEvent(const FGESEmitContext& EmitData, const GES_RAW_TEXT RawStringMessage)
{
	EmitEvent(EmitData, FString(RawStringMessage));
}

bool FGESHandler::EmitPropertyEvent(const FGESPropertyEmitContext& EmitData)
{
	//UE_LOG(LogTemp, Log, TEXT("World is: %s"), *EmitData.WorldContext.Get()->GetName());

	if (!EmitData.WorldContext || !EmitData.WorldContext->IsValidLowLevel())
	{
		//Remove this event, it's emit context is invalid
		DeleteEvent(EmitData.Domain, EmitData.Event);
		if (Options.bLogStaleRemovals)
		{
			UE_LOG(LogTemp, Log, TEXT("FGESHandler::EmitEvent stale event removed due to invalid world context for <%s.%s>. (Usually due to pinned events that haven't been unpinned)"),
				*EmitData.Domain, *EmitData.Event);
		}
		return false;
	}
	FProperty* ParameterProp = EmitData.Property;
	void* PropPtr = EmitData.PropertyPtr;

	//no params specified
	if (ParameterProp == nullptr)
	{
		EmitToListenersWithData(EmitData, [&EmitData](const FGESEventListener& Listener)
			{
				/*
				Never gets called?
				//C++ lambda case
				if (Listener.bIsBoundToLambda && Listener.LambdaFunction != nullptr)
				{
					FGESWildcardProperty Wrapper;
					Wrapper.Property = EmitData.Property;
					Wrapper.PropertyPtr = EmitData.PropertyPtr;

					Listener.LambdaFunction(Wrapper);
					return;
				}
				//If the listener bound it to a wildcard event delegate, emit with nullptr
				if (Listener.bIsBoundToDelegate)
				{
					FGESWildcardProperty Wrapper;
					Wrapper.Property = EmitData.Property;
					Wrapper.PropertyPtr = EmitData.PropertyPtr;
					Listener.OnePropertyFunctionDelegate.ExecuteIfBound(Wrapper);
					return;
				}*/

				//Neither lambda nor wildcard delegate, process no param prop
				TFieldIterator<FProperty> Iterator(Listener.Function);

				TArray<FProperty*> Properties;
				while (Iterator && (Iterator->PropertyFlags & CPF_Parm))
				{
					FProperty* Prop = *Iterator;
					Properties.Add(Prop);
					++Iterator;
				}
				if (Properties.Num() == 0)
				{
					Listener.ReceiverWCO->ProcessEvent(Listener.Function, nullptr);
				}
				else
				{
					UE_LOG(LogTemp, Warning, TEXT("FGESHandler::EmitEvent %s tried to emit an empty event to %s receiver expecting parameters."),
						*EmitData.Event,
						*Listener.ReceiverWCO->GetName());
				}
			});
	}
	else if (ParameterProp->IsA<FStructProperty>())
	{
		EmitSubPropertyEvent(EmitData);
		return true;
	}
	else if (ParameterProp->IsA<FStrProperty>())
	{
		EmitSubPropertyEvent(EmitData);
		return true;
	}
	else if (ParameterProp->IsA<FObjectProperty>())
	{
		EmitSubPropertyEvent(EmitData);
		return true;
	}
	else if (ParameterProp->IsA<FNumericProperty>())
	{
		//todo warn numeric mismatch again (int/float)
		EmitSubPropertyEvent(EmitData);
		return true;
	}
	else if (ParameterProp->IsA<FBoolProperty>())
	{
		EmitSubPropertyEvent(EmitData);
		return true;
	}
	else if (ParameterProp->IsA<FNameProperty>())
	{
		EmitSubPropertyEvent(EmitData);
		return true;
	}
	else
	{
		//Maps, Array, Sets etc unsupported atm
		UE_LOG(LogTemp, Warning, TEXT("FGESHandler::EmitEvent Unsupported parameter"));
		return false;
	}
	return false;
}

void FGESHandler::EmitSubPropertyEvent(const FGESPropertyEmitContext& EmitData)
{
	EmitToListenersWithData(EmitData, [&EmitData](const FGESEventListener& Listener)
	{
		if (FunctionHasValidParams(Listener.Function, EmitData.Property->StaticClass(), EmitData, Listener))
		{
			/*
			Never gets called?
			//Lambda Bind
			if (Listener.bIsBoundToLambda && Listener.LambdaFunction != nullptr)
			{
				FGESWildcardProperty Wrapper;
				Wrapper.Property = EmitData.Property;
				Wrapper.PropertyPtr = EmitData.PropertyPtr;

				Listener.LambdaFunction(Wrapper);
				return;
			}
			//Delegate Bind
			if (Listener.bIsBoundToDelegate)
			{
				FGESWildcardProperty Wrapper;
				Wrapper.Property = EmitData.Property;
				Wrapper.PropertyPtr = EmitData.PropertyPtr;
				Listener.OnePropertyFunctionDelegate.ExecuteIfBound(Wrapper);
				return;
			}*/

			//Standard Function Name Bind
			Listener.ReceiverWCO->ProcessEvent(Listener.Function, EmitData.PropertyPtr);
		}
	});
}

void FGESHandler::SetOptions(const FGESGlobalOptions& InOptions)
{
	Options = InOptions;
}

FString FGESHandler::Key(const FString& Domain, const FString& Event)
{
	return Domain + TEXT(".") + Event;
}

FGESHandler::FGESHandler()
{

}

FGESHandler::~FGESHandler()
{
	//Practically not needed due to shutdown happening on program exit
	/*for (TPair<FString, FGESEvent> Pair : FunctionMap)
	{
		if (Pair.Value.bPinned)
		{
			Pair.Value.PinnedData.CleanupPinnedData();
		}
	}*/
	EventMap.Empty();
}


================================================
FILE: Source/GlobalEventSystem/Private/GESHandlerDataTypes.cpp
================================================
#include "GESHandlerDataTypes.h"

void FGESPinnedData::CopyPropertyToPinnedBuffer()
{
	//Copy this property data to temp
	{
		//Workaround for our generated struct
		int32 Num = Property->GetSize();
		/*if (Property->IsA<FStructProperty>())
		{
			FStructProperty* StructProp = CastField<FStructProperty>(Property);
			if (StructProp->Struct)
			{
				Num = StructProp->Struct->PropertiesSize;
			}
		}*/
		
		PropertyData.SetNumUninitialized(Num);
		FMemory::Memcpy(PropertyData.GetData(), PropertyPtr, Num);

		//reset pointer to new copy
		PropertyPtr = PropertyData.GetData();
	}
}

void FGESPinnedData::CleanupPinnedData()
{
	PropertyData.Empty();

	//Some properties are being allocated in C++, we need to clean them here
	if (bHandlePropertyDeletion)
	{
		if (Property != nullptr)
		{
			Property->SetFlags(RF_BeginDestroyed);
		}
		delete Property;
	}
	Property = nullptr;
	PropertyPtr = nullptr;
}

FGESEvent::FGESEvent()
{
	PinnedData = FGESPinnedData();
}

FGESPropertyEmitContext::FGESPropertyEmitContext()
{
	Property = nullptr;
	PropertyPtr = nullptr;
	SpecificTarget = nullptr;
	bHandleAllocation = false;
}

FGESPropertyEmitContext::FGESPropertyEmitContext(const FGESEmitContext& Other)
{
	Domain = Other.Domain;
	Event = Other.Event;
	WorldContext = Other.WorldContext;
	bPinned = Other.bPinned;

	Property = nullptr;
	PropertyPtr = nullptr;
	SpecificTarget = nullptr;
}

FGESEvent::FGESEvent(const FGESEmitContext& Other)
{
	Domain = Other.Domain;
	Event = Other.Event;
	WorldContext = Other.WorldContext;
	bPinned = Other.bPinned;
}

FGESMinimalEventListener::FGESMinimalEventListener()
{
	ReceiverWCO = nullptr;
	FunctionName = TEXT("");
}

FGESEventListener::FGESEventListener()
{
	FGESMinimalEventListener();
	Function = nullptr;
	bIsBoundToDelegate = false;
	bIsBoundToLambda = false;
	LambdaFunction = nullptr;
}

FGESEventListener::FGESEventListener(const FGESMinimalEventListener& Minimal)
{
	ReceiverWCO = Minimal.ReceiverWCO;
	FunctionName = Minimal.FunctionName;
}

bool FGESEventListener::LinkFunction()
{
	Function = ReceiverWCO->FindFunction(FName(*FunctionName));
	return IsValidListener();
}

bool FGESEventListener::IsValidListener() const
{
	return (Function != nullptr || 
		bIsBoundToDelegate ||
		(bIsBoundToLambda && LambdaFunction != nullptr));
}




================================================
FILE: Source/GlobalEventSystem/Private/GESWorldListenerActor.cpp
================================================
// Copyright 2019-current Getnamo. All Rights Reserved


#include "GESWorldListenerActor.h"

// Sets default values
AGESWorldListenerActor::AGESWorldListenerActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;
	OnEndPlay = nullptr;
}

// Called when the game starts or when spawned
void AGESWorldListenerActor::BeginPlay()
{
	Super::BeginPlay();
	
}

void AGESWorldListenerActor::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	OnEndPlay();
	Super::EndPlay(EndPlayReason);
}



================================================
FILE: Source/GlobalEventSystem/Private/GlobalEventSystem.cpp
================================================
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#include "GlobalEventSystem.h"

#if WITH_EDITOR
#include "Editor.h"
#include "GESHandler.h"
#endif

#define LOCTEXT_NAMESPACE "FGlobalEventSystemModule"

void FGlobalEventSystemModule::StartupModule()
{
#if WITH_EDITOR
	EndPieDelegate = FEditorDelegates::BeginPIE.AddLambda([](bool boolSent)
	{
		UE_LOG(LogTemp, Warning, TEXT("Clearing FGESHandler"));
		FGESHandler::Clear();		
	});
#endif
}

void FGlobalEventSystemModule::ShutdownModule()
{
#if WITH_EDITOR
	FEditorDelegates::EndPIE.Remove(EndPieDelegate);
#endif
}

#undef LOCTEXT_NAMESPACE
	
IMPLEMENT_MODULE(FGlobalEventSystemModule, GlobalEventSystem)

================================================
FILE: Source/GlobalEventSystem/Private/GlobalEventSystemBPLibrary.cpp
================================================
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#include "GlobalEventSystemBPLibrary.h"
#include "GlobalEventSystem.h"

UGlobalEventSystemBPLibrary::UGlobalEventSystemBPLibrary(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}

void UGlobalEventSystemBPLibrary::GESUnbindEvent(UObject* WorldContextObject, const FString& Domain /*= TEXT("global.default")*/, const FString& Event /*= TEXT("")*/, const FString& ReceivingFunction /*= TEXT("")*/)
{
	FGESEventListener Listener;
	Listener.ReceiverWCO = WorldContextObject;
	Listener.FunctionName = ReceivingFunction;

	FGESHandler::DefaultHandler()->RemoveListener(Domain, Event, Listener);
}

void UGlobalEventSystemBPLibrary::GESUnbindTagEvent(UObject* WorldContextObject, FGameplayTag Tag, const FString& ReceivingFunction /*= TEXT("")*/)
{
	FString Domain;
	FString Event;
	Conv_TagToDomainAndEvent(Tag, Domain, Event);
	GESUnbindEvent(WorldContextObject, Domain, Event, ReceivingFunction);
}

void UGlobalEventSystemBPLibrary::GESUnbindAllEventsForContext(UObject* WorldContextObject, UObject* Context /*= nullptr*/)
{
	if (Context == nullptr)
	{
		Context = WorldContextObject;
	}
	FGESHandler::DefaultHandler()->RemoveAllListenersForReceiver(Context);
}

void UGlobalEventSystemBPLibrary::GESUnbindDelegate(UObject* WorldContextObject, const FGESOnePropertySignature& ReceivingFunction, const FString& Domain /*= TEXT("global.default")*/, const FString& Event /*= TEXT("")*/)
{
	FGESEventListener Listener;
	Listener.ReceiverWCO = WorldContextObject;

	if (ReceivingFunction.GetUObject()->IsValidLowLevelFast())
	{
		Listener.FunctionName = WorldContextObject->GetName() + ReceivingFunction.GetUObject()->GetName();
	}
	else
	{
		Listener.FunctionName = WorldContextObject->GetName() + TEXT(".UnboundDelegate");
	}
	Listener.OnePropertyFunctionDelegate = ReceivingFunction;
	Listener.bIsBoundToDelegate = true;

	FGESHandler::DefaultHandler()->RemoveListener(Domain, Event, Listener);
}

void UGlobalEventSystemBPLibrary::GESUnbindTagDelegate(UObject* WorldContextObject, FGameplayTag Tag, const FGESOnePropertySignature& ReceivingFunction)
{
	FString Domain;
	FString Event;
	Conv_TagToDomainAndEvent(Tag, Domain, Event);
	GESUnbindDelegate(WorldContextObject, ReceivingFunction, Domain, Event);
}

void UGlobalEventSystemBPLibrary::GESBindEvent(UObject* WorldContextObject, const FString& Domain /*= TEXT("global.default")*/, const FString& Event /*= TEXT("")*/, const FString& ReceivingFunction /*= TEXT("")*/)
{
	FGESEventListener Listener;
	Listener.ReceiverWCO = WorldContextObject;
	Listener.FunctionName = ReceivingFunction;
	Listener.LinkFunction();	//this makes the function valid by finding a reference to it

	FGESHandler::DefaultHandler()->AddListener(Domain, Event, Listener);
}

void UGlobalEventSystemBPLibrary::GESBindTagEvent(UObject* WorldContextObject, FGameplayTag DomainedEventTag, const FString& ReceivingFunction /*= TEXT("")*/)
{
	FGESEventListener Listener;
	Listener.ReceiverWCO = WorldContextObject;
	Listener.FunctionName = ReceivingFunction;
	Listener.LinkFunction();	//this makes the function valid by finding a reference to it

	FString Domain;
	FString Event;
	Conv_TagToDomainAndEvent(DomainedEventTag, Domain, Event);

	FGESHandler::DefaultHandler()->AddListener(Domain, Event, Listener);
}

void UGlobalEventSystemBPLibrary::GESBindTagEventToDelegate(UObject* WorldContextObject, FGameplayTag DomainedEventTag, const FGESOnePropertySignature& ReceivingFunction)
{
	FString Domain;
	FString Event;
	Conv_TagToDomainAndEvent(DomainedEventTag, Domain, Event);
	GESBindEventToDelegate(WorldContextObject, ReceivingFunction, Domain, Event);
}

void UGlobalEventSystemBPLibrary::GESBindEventToDelegate(UObject* WorldContextObject, const FGESOnePropertySignature& ReceivingFunction, const FString& Domain /*= TEXT("global.default")*/, const FString& Event /*= TEXT("")*/)
{
	FGESEventListener Listener;
	Listener.ReceiverWCO = WorldContextObject;
	if (ReceivingFunction.GetUObject()->IsValidLowLevelFast())
	{
		Listener.FunctionName = WorldContextObject->GetName() + ReceivingFunction.GetUObject()->GetName();
	}
	else
	{
		Listener.FunctionName = WorldContextObject->GetName() + TEXT(".UnboundDelegate");
	}
	Listener.OnePropertyFunctionDelegate = ReceivingFunction;
	Listener.bIsBoundToDelegate = true;

	FGESHandler::DefaultHandler()->AddListener(Domain, Event, Listener);
}

void UGlobalEventSystemBPLibrary::HandleEmit(const FGESPropertyEmitContext& FullEmitData)
{
	FGESHandler::DefaultHandler()->EmitPropertyEvent(FullEmitData);
}

void UGlobalEventSystemBPLibrary::GESEmitEventOneParam(UObject* WorldContextObject, TFieldPath<FProperty> ParameterData, bool bPinned /*= false*/, const FString& Domain /*= TEXT("global.default")*/, const FString& Event /*= TEXT("")*/)
{
	//this never gets called due to custom thunk
}

void UGlobalEventSystemBPLibrary::GESEmitEvent(UObject* WorldContextObject, bool bPinned /*= false*/, const FString& Domain /*= TEXT("global.default")*/, const FString& EventName /*= TEXT("")*/)
{
	if (!WorldContextObject)
	{
		return;
	}

	UWorld* World = WorldContextObject->GetWorld();
	if (!World)
	{
		return;
	}

	// Only allow real gameplay worlds
	if (!World->IsGameWorld())
	{
		return;
	}
	
	FGESEmitContext EmitData;
	EmitData.bPinned = bPinned;
	EmitData.Domain = Domain;
	EmitData.Event = EventName;
	EmitData.WorldContext = WorldContextObject;
	FGESHandler::DefaultHandler()->EmitEvent(EmitData);
}

void UGlobalEventSystemBPLibrary::GESEmitTagEvent(UObject* WorldContextObject, FGameplayTag DomainedEventTag, bool bPinned /*= false*/)
{
	FGESEmitContext EmitData;
	EmitData.bPinned = bPinned;
	Conv_TagToDomainAndEvent(DomainedEventTag, EmitData.Domain, EmitData.Event);
	EmitData.WorldContext = WorldContextObject;
	FGESHandler::DefaultHandler()->EmitEvent(EmitData);
}

void UGlobalEventSystemBPLibrary::GESEmitTagEventOneParam(UObject* WorldContextObject, TFieldPath<FProperty> ParameterData, FGameplayTag DomainedEventTag, bool bPinned /*= false*/)
{
	//this never gets called due to custom thunk
}

void UGlobalEventSystemBPLibrary::GESUnpinEvent(UObject* WorldContextObject, const FString& Domain /*= TEXT("global.default")*/, const FString& Event /*= TEXT("")*/)
{
	FGESHandler::DefaultHandler()->UnpinEvent(Domain, Event);
}

void UGlobalEventSystemBPLibrary::SetGESOptions(const FGESGlobalOptions& InOptions)
{
	FGESHandler::DefaultHandler()->SetOptions(InOptions);
}

bool UGlobalEventSystemBPLibrary::Conv_PropToInt(const FGESWildcardProperty& InProp, int32& OutInt)
{
	if (InProp.Property == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToInt InProp is a nullptr"));
		return false;
	}

	if (InProp.Property->IsA<FNumericProperty>())
	{
		FNumericProperty* Property = CastField<FNumericProperty>(InProp.Property.Get());
		if (!Property->IsFloatingPoint())
		{
			OutInt = Property->GetSignedIntPropertyValue(InProp.PropertyPtr);
			return true;
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToInt %s is not an integer number, float truncated to int."), *InProp.Property->GetName());
			OutInt = Property->GetFloatingPointPropertyValue(InProp.PropertyPtr);
			return false;
		}
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToInt %s is not an integer."), *InProp.Property->GetName());
		return false;
	}
}

bool UGlobalEventSystemBPLibrary::Conv_PropToFloat(const FGESWildcardProperty& InProp, float& OutFloat)
{
	if (InProp.Property == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToFloat InProp is a nullptr"));
		return false;
	}

	if (InProp.Property->IsA<FNumericProperty>())
	{
		FNumericProperty* Property = CastField<FNumericProperty>(InProp.Property.Get());
		if (Property->IsFloatingPoint())
		{
			OutFloat = Property->GetFloatingPointPropertyValue(InProp.PropertyPtr);
			return true;
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToFloat %s is not a floating number, converted int to float."), *InProp.Property->GetName());
			OutFloat = Property->GetSignedIntPropertyValue(InProp.PropertyPtr);
			return false;
		}
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToFloat %s is not a float."), *InProp.Property->GetName());
		return false;
	}
}

bool UGlobalEventSystemBPLibrary::Conv_PropToBool(const FGESWildcardProperty& InProp, bool& OutBool)
{
	if (InProp.Property == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToBool InProp is a nullptr"));
		return false;
	}

	if (InProp.Property->IsA<FBoolProperty>())
	{
		FBoolProperty* Property = CastField<FBoolProperty>(InProp.Property.Get());
		OutBool = Property->GetPropertyValue(InProp.PropertyPtr);
		return true;
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToBool %s is not a bool."), *InProp.Property->GetName());
		return false;
	}
}

bool UGlobalEventSystemBPLibrary::Conv_PropToStringRef(const FGESWildcardProperty& InProp, FString& OutString)
{
	if (InProp.Property == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToStringRef InProp is a nullptr"));
		return false;
	}

	if (InProp.Property->IsA<FStrProperty>())
	{
		FStrProperty* Property = CastField<FStrProperty>(InProp.Property.Get());
		OutString = Property->GetPropertyValue(InProp.PropertyPtr);
		return true;
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToString %s is not an FString, attempted best conversion for display purposes."), *InProp.Property->GetName());

		//Convert logic
		if (InProp.Property->IsA<FNumericProperty>())
		{
			FNumericProperty* Property = CastField<FNumericProperty>(InProp.Property.Get());
			if (Property->IsFloatingPoint())
			{
				OutString = FString::SanitizeFloat(Property->GetFloatingPointPropertyValue(InProp.PropertyPtr));
			}
			else
			{
				OutString = FString::FromInt(Property->GetSignedIntPropertyValue(InProp.PropertyPtr));
			}
		}
		else if (InProp.Property->IsA<FBoolProperty>())
		{
			FBoolProperty* Property = CastField<FBoolProperty>(InProp.Property.Get());
			if (Property->GetPropertyValue(InProp.PropertyPtr))
			{
				OutString = TEXT("True");
			}
			else
			{
				OutString = TEXT("False");
			}
		}
		else if (InProp.Property->IsA<FNameProperty>())
		{
			FNameProperty* Property = CastField <FNameProperty>(InProp.Property.Get());
			OutString = Property->GetPropertyValue(InProp.PropertyPtr).ToString();
		}
		else if (InProp.Property->IsA<FObjectProperty>())
		{
			FObjectProperty* Property = CastField<FObjectProperty>(InProp.Property.Get());
			UObject* Object = Property->GetPropertyValue(InProp.PropertyPtr);

			if (Object->IsValidLowLevelFast())
			{
				OutString = Object->GetName() + TEXT(", type: ") + Object->GetClass()->GetName();
			}
			else
			{
				OutString = TEXT("Null Object");
			}
		}
		else if (InProp.Property->IsA<FStructProperty>())
		{
			FStructProperty* Property = CastField<FStructProperty>(InProp.Property.Get());
			OutString = Property->GetName() + TEXT(", type: ") + Property->Struct->GetName();
		}
		return false;
	}
}

FString UGlobalEventSystemBPLibrary::Conv_PropToString(const FGESWildcardProperty& InProp)
{

	FString OutString;
	Conv_PropToStringRef(InProp, OutString);
	return OutString;
}

bool UGlobalEventSystemBPLibrary::Conv_PropToName(const FGESWildcardProperty& InProp, FName& OutName)
{
	if (InProp.Property == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToName InProp is a nullptr"));
		return false;
	}

	if (InProp.Property->IsA<FNameProperty>())
	{
		FNameProperty* Property = CastField<FNameProperty>(InProp.Property.Get());
		OutName = Property->GetPropertyValue(InProp.PropertyPtr);
		return true;
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToName %s is not an FName."), *InProp.Property->GetName());
		return false;
	}
}

bool UGlobalEventSystemBPLibrary::Conv_PropToStruct(const FGESWildcardProperty& InProp, TFieldPath<FProperty>& OutStruct)
{
	//doesn't get called due to custom thunk
	return false;
}

bool UGlobalEventSystemBPLibrary::HandlePropToStruct(const FGESWildcardProperty& InProp, FGESWildcardProperty& OutProp)
{
	if (InProp.Property == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::HandlePropToStruct InProp is a nullptr"));
		return false;
	}
	if (OutProp.Property == nullptr)
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::HandlePropToStruct OutProp is a nullptr"));
		return false;
	}

	if (InProp.Property->IsA<FStructProperty>() && OutProp.Property->IsA<FStructProperty>())
	{
		FStructProperty* InStructProp = CastField<FStructProperty>(InProp.Property.Get());
		FStructProperty* OutStructProp = CastField<FStructProperty>(OutProp.Property.Get());

		OutStructProp->CopyCompleteValue(OutProp.PropertyPtr, InProp.PropertyPtr);
		return true;
	}
	else
	{
		return false;
	}
}

bool UGlobalEventSystemBPLibrary::Conv_PropToObject(const FGESWildcardProperty& InProp, UObject*& OutObject)
{
	if (InProp.Property == nullptr)
	{
		return false;
	}

	if (InProp.Property->IsA<FObjectProperty>())
	{
		FObjectProperty* Property = CastField<FObjectProperty>(InProp.Property.Get());
		OutObject = Property->GetPropertyValue(InProp.PropertyPtr);
		return true;
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("UGlobalEventSystemBPLibrary::Conv_PropToObject %s is not an Object."), *InProp.Property->GetName());
		return false;
	}
}

void UGlobalEventSystemBPLibrary::Conv_TagToDomainAndEvent(FGameplayTag InTag, FString& OutDomain, FString& OutEvent)
{
	FString DomainAndEvent = InTag.GetTagName().ToString();

	bool bFound = DomainAndEvent.Split(TEXT("."), &OutDomain, &OutEvent, ESearchCase::IgnoreCase, ESearchDir::FromEnd);

	if (!bFound)
	{
		OutDomain = TEXT("global.default");
		OutEvent = DomainAndEvent;
	}
}



================================================
FILE: Source/GlobalEventSystem/Public/GESBaseReceiverComponent.h
================================================
// Copyright 2019-current Getnamo. All Rights Reserved

#pragma once

#include "Components/ActorComponent.h"
#include "GESDataTypes.h"
#include "GESHandlerDataTypes.h"
#include "GESBaseReceiverComponent.generated.h"

/** Convenience base class for receiving GES events in an organized way for actors.*/
UCLASS(BlueprintType, Blueprintable, ClassGroup = "Utility", meta = (BlueprintSpawnableComponent))
class GLOBALEVENTSYSTEM_API UGESBaseReceiverComponent : public UActorComponent
{
	GENERATED_UCLASS_BODY()
public:
	//Wildcard receiver
	UPROPERTY(BlueprintAssignable, Category = "GES Receiver")
	FGESOnePropertyMCSignature OnEvent;

	//Domain, Event, and receiving function name
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GES Receiver")
	FGESNameBind BindSettings;

	//For polling after having received an event
	UPROPERTY(BlueprintReadOnly, Category = "GES Receiver")
	FGESWildcardProperty LastReceivedProperty;

	//auto-bind as soon as this component begins play
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GES Receiver")
	bool bBindOnBeginPlay;

	//Unbind the event automatically whenever gameplay ends for this component (e.g. destroyed)
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GES Receiver")
	bool bUnbindOnEndPlay;

	/**
	* If event is the wildcard component one, this will pin data received for polling.
	* Turned off only for optimization generally.
	*/
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GES Receiver")
	bool bPinInternalDataForPolling;

	/** Used to know if polling for last data will give valid results */
	UPROPERTY(BlueprintReadWrite, Category = "GES Receiver")
	bool bDidReceiveEventAtLeastOnce;

	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

protected:

	//Only used to route signal to MC variant
	UPROPERTY()
	FGESOnePropertySignature InternalListener;

	FGESPinnedData PinnedData;

	UFUNCTION()
	void HandleInternalEvent(const FGESWildcardProperty& WildcardProperty);
};

================================================
FILE: Source/GlobalEventSystem/Public/GESDataTypes.h
================================================
#pragma once

#include "CoreMinimal.h"
#include "GESDataTypes.generated.h"

/** 
* Global options for GESHandler. Used in BP library static calls.
*/
USTRUCT(BlueprintType)
struct FGESGlobalOptions
{
	GENERATED_BODY()

	/** Whether to ensure structs are exactly the same. Turn off for small performance boost. Default true.*/
	UPROPERTY(BlueprintReadWrite, Category = "GES Global Options")
	bool bValidateStructTypes;

	/** Will output logs for stale events and listeners that get removed. . Default true.*/
	UPROPERTY(BlueprintReadWrite, Category = "GES Global Options")
	bool bLogStaleRemovals;

	FGESGlobalOptions()
	{
		bValidateStructTypes = true;
		bLogStaleRemovals = true;
	}
};

/** Struct used to define a bind to a GES event by function name. (Used in GESBaseReceiverComponents) */
USTRUCT(BlueprintType)
struct FGESNameBind
{
	GENERATED_BODY()

	/** Abstract Domain name used in GES, similar to a channel concept. */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GES Local Bind")
	FString Domain;
	
	/** Abstract event name used in GES. Unique when combined with Domain. */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GES Local Bind")
	FString Event;

	/** Name of function receiving event. */
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "GES Local Bind")
	FString ReceivingFunction;

	FGESNameBind()
	{
		Domain = TEXT("global.default");
		Event = TEXT("");
		ReceivingFunction = TEXT("");
	}
};

/** 
*	Wrapper for lambda bind call data minus actual receiver function.
*	Used in AddLambdaListener and remove variant.
*/
USTRUCT()
struct FGESEventContext
{
	GENERATED_BODY()

	/** Abstract Domain name used in GES, similar to a channel concept. */
	UPROPERTY()
	FString Domain;

	/** Abstract event name used in GES. Unique when combined with Domain. */
	UPROPERTY()
	FString Event;

	/** World context object. Used to determine max lifetime of event. */
	UPROPERTY()
	UObject* WorldContext;

	FGESEventContext()
	{
		Domain = TEXT("global.default");
		Event = TEXT("");
		WorldContext = nullptr;
	}
};

USTRUCT()
struct FGESEmitContext : public FGESEventContext
{
	GENERATED_BODY()

	/** Pinned means an emitted event state should be accessible after it has been emitted. */
	UPROPERTY()
	bool bPinned;

	FGESEmitContext()
	{
		Domain = TEXT("global.default");
		Event = TEXT("");
		WorldContext = nullptr;
		bPinned = false;
	}
};


/** 
* Wrapper struct for a wildcard property. Allows directly binding GES events to
* delegate events with GESBPLibrary conversion casting used to specialize the property.
*/
USTRUCT(BlueprintType)
struct FGESWildcardProperty
{
	GENERATED_BODY()

	/** Property wrapper. Use GESBPLibrary conversion functions to obtained specialized conversions.*/
	UPROPERTY(BlueprintReadOnly, Category = "GES Global Options")
	TFieldPath<FProperty> Property;

	void* PropertyPtr;
};

/** No param Delegate */
DECLARE_DYNAMIC_DELEGATE(FGESEmptySignature);

/** Wildcard Delegate */
DECLARE_DYNAMIC_DELEGATE_OneParam(FGESOnePropertySignature, const FGESWildcardProperty&, WildcardProperty);

/** Multicast variant of Wildcard Delegate (used in BaseReceiverComponents) */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGESOnePropertyMCSignature, const FGESWildcardProperty&, WildcardProperty);

================================================
FILE: Source/GlobalEventSystem/Public/GESHandler.h
================================================
#pragma once
#include "UObject/Object.h"
#include "UObject/UnrealType.h"
#include "GESWorldListenerActor.h"
#include "GESDataTypes.h"
#include "GESHandlerDataTypes.h"

//Text macro to handle TEXT("") emits
#if !defined(GES_RAW_TEXT)
	#if PLATFORM_TCHAR_IS_CHAR16
		#define GES_RAW_TEXT char16_t*
	#else
		#define GES_RAW_TEXT wchar_t*
	#endif
#endif

/** 
GESHandler Class usable in C++ with care. Private API may be a bit too exposed atm.
*/

class GLOBALEVENTSYSTEM_API FGESHandler
{
public:

	//Get the Global (default) handler to call all other functions
	static TSharedPtr<FGESHandler> DefaultHandler();

	/**
	*   Clear all listeners and reset state
	*/
	static void Clear();

	/**
	*	Create an event in TargetDomain.TargetFunction. Does nothing if already existing.
	*/
	void CreateEvent(const FString& Domain, const FString& Event, bool bPinned = false);
	
	/**
	*	Delete an event in TargetDomain.TargetFunction. Does nothing if missing.
	*/
	void DeleteEvent(const FString& Domain, const FString& Event);

	/** 
	*	Delete an event in with DomainAndEvent defined as a single string. Does nothing if missing.
	*/
	void DeleteEvent(const FString& DomainAndEvent);

	/** 
	*	Check if event exists
	*/
	bool HasEvent(const FString& Domain, const FString& Event);

	/** 
	*	Removes the pinning of the event for future listeners.
	*/
	void UnpinEvent(const FString& Domain, const FString& Event);

	/** 
	* Listen to an event in TargetDomain.TargetFunction via generic listener
	*/
	void AddListener(const FString& Domain, const FString& Event, const FGESEventListener& Listener);

	/**
	*	Listen to an event in TargetDomain.TargetFunction via passed in lambda
	*/
	FString AddLambdaListener(FGESEventContext EventInfo, TFunction<void(const FGESWildcardProperty&)> ReceivingLambda);
	
	/**
	* Stop listening to an event in TargetDomain.TargetFunction
	*/
	void RemoveListener(const FString& Domain, const FString& Event, const FGESEventListener& Listener);

	/**
	* Stop listening to all events for given receiver
	*/
	void RemoveAllListenersForReceiver(UObject* ReceiverWCO);

	/**
	*	Listen to an event in TargetDomain.TargetFunction via passed in lambda
	*/
	void RemoveLambdaListener(FGESEventContext EventInfo, TFunction<void(const FGESWildcardProperty&)> ReceivingLambda);

	/** 
	*	Remove lambda by function id string. Used in case of lambdas without ref to initial function.
	*/
	void RemoveLambdaListener(FGESEventContext EventInfo, const FString& LambdaName);

	//overloaded emits
	void EmitEvent(const FGESEmitContext& EmitData, UStruct* Struct, void* StructPtr);
	void EmitEvent(const FGESEmitContext& EmitData, const FString& ParamData);
	void EmitEvent(const FGESEmitContext& EmitData, UObject* ParamData);
	void EmitEvent(const FGESEmitContext& EmitData, float ParamData);
	void EmitEvent(const FGESEmitContext& EmitData, int32 ParamData);
	void EmitEvent(const FGESEmitContext& EmitData, bool ParamData);
	void EmitEvent(const FGESEmitContext& EmitData, const FName& ParamData);
	bool EmitEvent(const FGESEmitContext& EmitData);
	//GES_RAW_TEXT supports passing in TEXT("") macros
	void EmitEvent(const FGESEmitContext& EmitData, const GES_RAW_TEXT RawStringMessage);

	//processed means the pointers have been filled
	bool EmitPropertyEvent(const FGESPropertyEmitContext& FullEmitData);

	//overloaded lambda binds
	FString AddLambdaListener(FGESEventContext EventInfo, TFunction<void(UStruct* Struct, void* StructPtr)> ReceivingLambda);
	FString AddLambdaListener(FGESEventContext EventInfo, TFunction<void(const FString&)> ReceivingLambda);
	FString AddLambdaListener(FGESEventContext EventInfo, TFunction<void(UObject*)> ReceivingLambda);
	FString AddLambdaListener(FGESEventContext EventInfo, TFunction<void(float)> ReceivingLambda);
	FString AddLambdaListener(FGESEventContext EventInfo, TFunction<void(const FName&)> ReceivingLambda);
	FString AddLambdaListener(FGESEventContext EventInfo, TFunction<void(void)> ReceivingLambda);

	//needed unique names due to ambiguity clash with float
	FString AddLambdaListenerInt(FGESEventContext EventInfo, TFunction<void(int32)> ReceivingLambda);
	FString AddLambdaListenerBool(FGESEventContext EventInfo, TFunction<void(bool)> ReceivingLambda);

	/**
	* Update global options
	*/
	void SetOptions(const FGESGlobalOptions& InOptions);
	
	/** 
	* Convenience internal Key for domain and event string
	*/
	static FString Key(const FString& Domain, const FString& Event);

	FGESHandler();
	~FGESHandler();

private:
	static TSharedPtr<FGESHandler> PrivateDefaultHandler;

	//internal helper for in-context data filling for listeners
	void EmitToListenersWithData(const FGESPropertyEmitContext& EmitData, TFunction<void(const FGESEventListener&)> DataFillCallback);
	//internal emitter to each listener
	bool EmitToListenerWithData(const FGESPropertyEmitContext& EmitData, const FGESEventListener& Listener,
		TFunction<void(const FGESEventListener&)>& DataFillCallback);

	//internal overloads
	void EmitSubPropertyEvent(const FGESPropertyEmitContext& EmitData);

	//can check function signature vs e.g. FString
	static bool FirstParamIsCppType(UFunction* Function, const FString& TypeString);
	static bool FirstParamIsSubclassOf(UFunction* Function, FFieldClass* ClassType);
	static FString ListenerLogString(const FGESEventListener& Listener);
	static FString EventLogString(const FGESEvent& Event);
	static FString EmitEventLogString(const FGESEmitContext& EmitData);
	static void FunctionParameters(UFunction* Function, TArray<FProperty*>& OutParamProperties);

	//this function logs warnings otherwise
	static bool FunctionHasValidParams(UFunction* Function, FFieldClass* ClassType, const FGESEmitContext& EmitData, const FGESEventListener& Listener);

	//Key == TargetDomain.TargetFunction
	TMap<FString, FGESEvent> EventMap;
	TMap<UObject*, TArray<FGESEventListenerWithContext>> ReceiverMap;
	TArray<FGESEventListener*> RemovalArray;

	//Toggles
	FGESGlobalOptions Options;

	TMap<UWorld*, AGESWorldListenerActor*> WorldMap;
};

================================================
FILE: Source/GlobalEventSystem/Public/GESHandlerDataTypes.h
================================================
#pragma once

#include "GESDataTypes.h"

/** Struct to hold pinned property data */
struct FGESPinnedData
{
	FProperty* Property;
	void* PropertyPtr;
	TArray<uint8> PropertyData;
	bool bHandlePropertyDeletion;

	FGESPinnedData()
	{
		Property = nullptr;
		PropertyPtr = nullptr;
		bHandlePropertyDeletion = false;
	}
	~FGESPinnedData()
	{
		CleanupPinnedData();
	}
	void CopyPropertyToPinnedBuffer();
	void CleanupPinnedData();
};

struct FGESDynamicArg
{
	void* Arg01;
};

//Minimal definition to define a listener (for removal)
struct FGESMinimalEventListener
{
	TWeakObjectPtr<UObject> ReceiverWCO;	//WorldContextObject
	FString FunctionName;

	bool operator ==(FGESMinimalEventListener const& Other)
	{
		return (Other.ReceiverWCO == ReceiverWCO) && (Other.FunctionName == FunctionName);
	}
	FGESMinimalEventListener();
};

struct FGESEventListener : FGESMinimalEventListener
{
	// Opt A) Bound UFunction, valid after calling LinkFunction
	UFunction* Function;

	// Opt B) Bound to a delegate
	bool bIsBoundToDelegate;
	FGESOnePropertySignature OnePropertyFunctionDelegate;

	// Opt C) Bound to a lambda function
	bool bIsBoundToLambda;
	TFunction<void(const FGESWildcardProperty&)> LambdaFunction;

	FGESEventListener(const FGESMinimalEventListener& Minimal);
	FGESEventListener();
	bool LinkFunction();
	bool IsValidListener() const;
};

//Wrapper struct for tracking event-receiver pairs in ReceiverMap
struct FGESEventListenerWithContext
{
	FGESMinimalEventListener Listener;
	FString Domain;
	FString Event;

	FGESEventListenerWithContext()
	{
		Domain = TEXT("");
		Event = TEXT("");
	}

	bool operator ==(FGESEventListenerWithContext const& Other)
	{
		return (Other.Domain == Domain) && 
			(Other.Event == Event) &&
			(Listener.FunctionName == Other.Listener.FunctionName) &&
			(Listener.ReceiverWCO == Other.Listener.ReceiverWCO);
	}
};

//Event specialization with pinned and listener data
struct FGESEvent : FGESEmitContext
{
	//If pinned an event will emit the moment you add a listener if it has been already fired once
	FGESPinnedData PinnedData;

	TArray<FGESEventListener> Listeners;

	FGESEvent();
	FGESEvent(const FGESEmitContext& Other);
};

//Emit specialization with property pointers
struct FGESPropertyEmitContext : FGESEmitContext
{
	FProperty* Property;
	void* PropertyPtr;
	bool bHandleAllocation;

	//NB: if we want a callback or pin emit
	FGESEventListener* SpecificTarget;

	FGESPropertyEmitContext();
	FGESPropertyEmitContext(const FGESEmitContext& Other);
};

================================================
FILE: Source/GlobalEventSystem/Public/GESWorldListenerActor.h
================================================
// Copyright 2019-current Getnamo. All Rights Reserved

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "GESWorldListenerActor.generated.h"

/** 
* An actor spawned per world by FGESHandler in order to track when the world
* gets torn down and we have to remove all listeners for that world.
*/
UCLASS()
class GLOBALEVENTSYSTEM_API AGESWorldListenerActor : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AGESWorldListenerActor();

	// Event to listen to in order to catch world ending
	TFunction<void()> OnEndPlay;

	TSet<FString> WorldEvents;

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
};


================================================
FILE: Source/GlobalEventSystem/Public/GlobalEventSystem.h
================================================
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "Modules/ModuleManager.h"

class FGlobalEventSystemModule : public IModuleInterface
{
public:

	/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;

private:

#if WITH_EDITOR
	FDelegateHandle EndPieDelegate;
#endif
};


================================================
FILE: Source/GlobalEventSystem/Public/GlobalEventSystemBPLibrary.h
================================================
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#pragma once

#include "Kismet/BlueprintFunctionLibrary.h"
#include "GESHandler.h"
#include "GameplayTagContainer.h"
#include "GlobalEventSystemBPLibrary.generated.h"

/* 
* Core Global Event System functions. Call anywhere.
*/
UCLASS()
class GLOBALEVENTSYSTEM_API UGlobalEventSystemBPLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_UCLASS_BODY()

	/** 
	* Remove this listener from the specified GESEvent.
	*/
	UFUNCTION(BlueprintCallable, meta = (Keywords = "ges sever stoplisten", WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESUnbindEvent(UObject* WorldContextObject, const FString& Domain = TEXT("global.default"), const FString& Event = TEXT(""), const FString& ReceivingFunction = TEXT(""));

	/**
	* Remove this listener from the specified GESEvent given by GameplayTag.
	*/
	UFUNCTION(BlueprintCallable, meta = (Keywords = "ges sever stoplisten", WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESUnbindTagEvent(UObject* WorldContextObject, FGameplayTag Tag, const FString& ReceivingFunction = TEXT(""));


	/** 
	* Call this on endplay to remove all events associated with this graph. If context isn't specified, graph context used (default use case).
	*/
	UFUNCTION(BlueprintCallable, meta = (Keywords = "ges sever stoplisten", WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESUnbindAllEventsForContext(UObject* WorldContextObject, UObject* Context = nullptr);


	UFUNCTION(BlueprintCallable, meta = (Keywords = "ges sever stoplisten", WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESUnbindDelegate(UObject* WorldContextObject, const FGESOnePropertySignature& ReceivingFunction, const FString& Domain = TEXT("global.default"), const FString& Event = TEXT(""));

	UFUNCTION(BlueprintCallable, meta = (Keywords = "ges sever stoplisten", WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESUnbindTagDelegate(UObject* WorldContextObject, FGameplayTag Tag, const FGESOnePropertySignature& ReceivingFunction);

	/**
	* Bind a function (to current caller) to GES event. Make sure to match your receiving function parameters to the GESEvent ones.
	*/
	UFUNCTION(BlueprintCallable, meta = (Keywords = "ges create listen", WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESBindEvent(UObject* WorldContextObject, const FString& Domain = TEXT("global.default"), const FString& Event = TEXT(""), const FString& ReceivingFunction = TEXT(""));

	/**
	* Bind a function (to current caller) to GES event defined by a GamePlayTag
	*/
	UFUNCTION(BlueprintCallable, meta = (Keywords = "ges create listen", WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESBindTagEvent(UObject* WorldContextObject, FGameplayTag DomainedEventTag, const FString& ReceivingFunction = TEXT(""));

	/**
	* Bind a function (to current caller) to GES event defined by a GamePlayTag
	*/
	UFUNCTION(BlueprintCallable, meta = (Keywords = "ges create listen", WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESBindTagEventToDelegate(UObject* WorldContextObject, FGameplayTag DomainedEventTag, const FGESOnePropertySignature& ReceivingFunction);

	/**
	* Bind an event delegate to GES event. Use blueprint utility to decode UProperty.
	*/
	UFUNCTION(BlueprintCallable, meta = (Keywords = "ges create listen", WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESBindEventToDelegate(UObject* WorldContextObject, const FGESOnePropertySignature& ReceivingFunction, const FString& Domain = TEXT("global.default"), const FString& Event = TEXT(""));

	/** 
	* Emit desired event with data. Data can be any single property (wrap arrays/maps etc in a struct or object)
	* Pinning an event means it will emit to future listeners even if the event has already been
	* emitted.
	*/
	UFUNCTION(BlueprintCallable, CustomThunk, Category = "GlobalEventSystem", meta = (CustomStructureParam = "ParameterData", WorldContext = "WorldContextObject"))
	static void GESEmitEventOneParam(UObject* WorldContextObject, TFieldPath<FProperty> ParameterData, bool bPinned = false, const FString& Domain = TEXT("global.default"), const FString& Event = TEXT(""));

	/** 
	* Just emits the event with no additional data
	* Pinning an event means it will emit to future listeners even if the event has already been
	* emitted.
	*/
	UFUNCTION(BlueprintCallable, meta=(WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESEmitEvent(UObject* WorldContextObject, bool bPinned = false, const FString& Domain = TEXT("global.default"), const FString& Event = TEXT(""));

	/**
	* Just emits the event with no additional data using GameplayTags to define domain and event.
	* Pinning an event means it will emit to future listeners even if the event has already been
	* emitted.
	*/
	UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESEmitTagEvent(UObject* WorldContextObject, FGameplayTag DomainedEventTag, bool bPinned = false);

	/**
	* Emit desired event with data using GameplayTags to define domain and event. Data can be any single
	* property (wrap arrays/maps etc in a struct or object).
	* Pinning an event means it will emit to future listeners even if the event has already been
	* emitted.
	*/
	UFUNCTION(BlueprintCallable, CustomThunk, Category = "GlobalEventSystem", meta = (CustomStructureParam = "ParameterData", WorldContext = "WorldContextObject"))
	static void GESEmitTagEventOneParam(UObject* WorldContextObject, TFieldPath<FProperty> ParameterData, FGameplayTag DomainedEventTag, bool bPinned = false);

	/** 
	* If an event was pinned, this will unpin it. If you wish to re-pin a different event you need to unpin the old event first.
	*/
	UFUNCTION(BlueprintCallable, meta = (WorldContext = "WorldContextObject"), Category = "GlobalEventSystem")
	static void GESUnpinEvent(UObject* WorldContextObject, const FString& Domain = TEXT("global.default"), const FString& Event = TEXT(""));

	/**
	* GES Options are global and affect things like logging and param verification (performance options)
	*/
	UFUNCTION(BlueprintCallable, Category = "GlobalEventSystemOptions")
	static void SetGESOptions(const FGESGlobalOptions& InOptions);

	//Wildcard conversions, used in wildcard event delegates from GESBindEventToWildcardDelegate

	/** Convert wildcard property into a literal int */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Integer (Wildcard Property)", BlueprintAutocast), Category = "Utilities|GES")
	static bool Conv_PropToInt(const FGESWildcardProperty& InProp, int32& OutInt);

	/** Convert wildcard property into a literal float */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Float (Wildcard Property)", BlueprintAutocast), Category = "Utilities|GES")
	static bool Conv_PropToFloat(const FGESWildcardProperty& InProp, float& OutFloat);

	/** Convert wildcard property into a literal bool */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Bool (Wildcard Property)", BlueprintAutocast), Category = "Utilities|GES")
	static bool Conv_PropToBool(const FGESWildcardProperty& InProp, bool& OutBool);

	/** Convert wildcard property into a string (reference) */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To String Ref with status (Wildcard Property)"), Category = "Utilities|GES")
	static bool Conv_PropToStringRef(const FGESWildcardProperty& InProp, FString& OutString);

	/** Will still warn, but won't return a boolean for conversion status, used for auto-casting to print strings for debugging */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To String (Wildcard Property)", BlueprintAutocast), Category = "Utilities|GES")
	static FString Conv_PropToString(const FGESWildcardProperty& InProp);

	/** Convert wildcard property into a literal Name */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Name (Wildcard Property)", BlueprintAutocast), Category = "Utilities|GES")
	static bool Conv_PropToName(const FGESWildcardProperty& InProp, FName& OutName);

	/** Convert wildcard property into any struct */
	UFUNCTION(BlueprintPure, CustomThunk, meta = (DisplayName = "To Struct (Wildcard Property)", CustomStructureParam = "OutStruct", BlueprintAutocast), Category = "Utilities|GES")
	static bool Conv_PropToStruct(const FGESWildcardProperty& InProp, TFieldPath<FProperty>& OutStruct);

	/** Convert wildcard property into any Object */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Object (Wildcard Property)", BlueprintAutocast), Category = "Utilities|GES")
	static bool Conv_PropToObject(const FGESWildcardProperty& InProp, UObject*& OutObject);

	/** Convert a GameplayTag into a Domain and Event string pair */
	UFUNCTION(BlueprintPure, meta = (DisplayName = "To Domain and Event (GameplayTag)", BlueprintAutocast), Category = "Utilities|GES")
	static void Conv_TagToDomainAndEvent(FGameplayTag InTag, FString& OutDomain, FString& OutEvent);

	//Convert property into c++ accessible form
	DECLARE_FUNCTION(execGESEmitEventOneParam)
	{
		Stack.MostRecentProperty = nullptr;
		FGESPropertyEmitContext EmitData;

		Stack.StepCompiledIn<FObjectProperty>(&EmitData.WorldContext);

		//Determine wildcard property
		Stack.Step(Stack.Object, NULL);
		FProperty* ParameterProp = CastField<FProperty>(Stack.MostRecentProperty);
		void* PropPtr = Stack.MostRecentPropertyAddress;

		EmitData.Property = ParameterProp;
		EmitData.PropertyPtr = PropPtr;

		Stack.StepCompiledIn<FBoolProperty>(&EmitData.bPinned);
		Stack.StepCompiledIn<FStrProperty>(&EmitData.Domain);
		Stack.StepCompiledIn<FStrProperty>(&EmitData.Event);

		P_FINISH;
		P_NATIVE_BEGIN;
		HandleEmit(EmitData);
		P_NATIVE_END;
	}

	DECLARE_FUNCTION(execGESEmitTagEventOneParam)
	{
		Stack.MostRecentProperty = nullptr;
		FGESPropertyEmitContext EmitData;

		Stack.StepCompiledIn<FObjectProperty>(&EmitData.WorldContext);

		//Determine wildcard property
		Stack.Step(Stack.Object, NULL);
		if (Stack.MostRecentProperty != nullptr)
		{
			EmitData.Property = CastField<FProperty>(Stack.MostRecentProperty);
			EmitData.PropertyPtr = Stack.MostRecentPropertyAddress;
		}
		
		FGameplayTag Tag;
		Stack.StepCompiledIn<FStructProperty>(&Tag);
		Conv_TagToDomainAndEvent(Tag, EmitData.Domain, EmitData.Event);

		
		Stack.StepCompiledIn<FBoolProperty>(&EmitData.bPinned);

		P_FINISH;
		P_NATIVE_BEGIN;
		HandleEmit(EmitData);
		P_NATIVE_END;
	}

	DECLARE_FUNCTION(execConv_PropToStruct)
	{
		Stack.MostRecentProperty = nullptr;
		FGESWildcardProperty InProp;
		FGESWildcardProperty OutProp;
		
		//Determine copy wildcard property variables
		Stack.StepCompiledIn<FStructProperty>(&InProp);		
		//Stack.Step(Stack.Object, NULL);
		//InProp.Property = CastField<FProperty>(Stack.MostRecentProperty);
		//InProp.PropertyPtr = Stack.MostRecentPropertyAddress;

		//Copy the out struct property address
		//Stack.StepCompiledIn<FStructProperty>(&OutProp);
		Stack.Step(Stack.Object, NULL);
		FProperty* ParameterProp = CastField<FProperty>(Stack.MostRecentProperty);
		void* PropPtr = Stack.MostRecentPropertyAddress;

		OutProp.Property = ParameterProp;
		OutProp.PropertyPtr = PropPtr;
		bool bDidCopy = false;

		P_FINISH;
		P_NATIVE_BEGIN;
		bDidCopy = HandlePropToStruct(InProp, OutProp);
		P_NATIVE_END;

		*(bool*)RESULT_PARAM = bDidCopy;
	}


private:
	static void HandleEmit(const FGESPropertyEmitContext& EmitData);
	static bool HandlePropToStruct(const FGESWildcardProperty& InProp, FGESWildcardProperty& FullProp);
};
Download .txt
gitextract_fmvxu3bk/

├── .gitignore
├── Content/
│   ├── GenericComponentReceivers/
│   │   ├── BoolGESReceiverComponent.uasset
│   │   ├── FloatGESReceiverComponent.uasset
│   │   ├── IntGESReceiverComponent.uasset
│   │   ├── ObjectGESReceiverComponent.uasset
│   │   ├── RotatorGESReceiverComponent.uasset
│   │   ├── StringGESReceiverComponent.uasset
│   │   ├── TransformGESReceiverComponent.uasset
│   │   └── VectorGESReceiverComponent.uasset
│   ├── Javascript/
│   │   └── GESJsReceiverBpActor.uasset
│   ├── Macros/
│   │   └── GESMacroLibrary.uasset
│   └── Scripts/
│       └── ges/
│           └── gesWrapper.js
├── GlobalEventSystem.uplugin
├── LICENSE
├── README.md
└── Source/
    └── GlobalEventSystem/
        ├── GlobalEventSystem.Build.cs
        ├── Private/
        │   ├── GESBaseReceiverComponent.cpp
        │   ├── GESDataTypes.cpp
        │   ├── GESHandler.cpp
        │   ├── GESHandlerDataTypes.cpp
        │   ├── GESWorldListenerActor.cpp
        │   ├── GlobalEventSystem.cpp
        │   └── GlobalEventSystemBPLibrary.cpp
        └── Public/
            ├── GESBaseReceiverComponent.h
            ├── GESDataTypes.h
            ├── GESHandler.h
            ├── GESHandlerDataTypes.h
            ├── GESWorldListenerActor.h
            ├── GlobalEventSystem.h
            └── GlobalEventSystemBPLibrary.h
Download .txt
SYMBOL INDEX (43 symbols across 11 files)

FILE: Content/Scripts/ges/gesWrapper.js
  class GESJsReceiver (line 11) | class GESJsReceiver extends JsOwner.ClassMap['GESJsReceiverBpActor']{
    method ctor (line 12) | ctor(){
    method constructor (line 15) | constructor(){
    method OnJsReceive (line 18) | OnJsReceive(uniqueId, property){
    method OnJsReceiveObj (line 22) | OnJsReceiveObj(uniqueId, property){
    method bind (line 27) | bind(domain='global.default', event, callback){
    method bindToObjCallback (line 43) | bindToObjCallback(domain='global.default', event, callback, uniqueRece...
    method emit (line 66) | emit(domain='global.default', event, data='', pinned=false){
    method unbind (line 76) | unbind(domain='global.default', event, uniqueFunctionId){
    method wlog (line 80) | wlog(text){
    method unbindAll (line 86) | unbindAll(){

FILE: Source/GlobalEventSystem/GlobalEventSystem.Build.cs
  class GlobalEventSystem (line 5) | public class GlobalEventSystem : ModuleRules
    method GlobalEventSystem (line 7) | public GlobalEventSystem(ReadOnlyTargetRules Target) : base(Target)

FILE: Source/GlobalEventSystem/Private/GESHandler.cpp
  function FString (line 36) | FString FGESHandler::ListenerLogString(const FGESEventListener& Listener)
  function FString (line 41) | FString FGESHandler::EventLogString(const FGESEvent& Event)
  function FString (line 46) | FString FGESHandler::EmitEventLogString(const FGESEmitContext& EmitData)
  function FString (line 206) | FString FGESHandler::AddLambdaListener(FGESEventContext Context, TFuncti...
  function FString (line 227) | FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunct...
  function FString (line 244) | FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunct...
  function FString (line 255) | FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunct...
  function FString (line 266) | FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunct...
  function FString (line 277) | FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunct...
  function FString (line 288) | FString FGESHandler::AddLambdaListener(FGESEventContext BindInfo, TFunct...
  function FString (line 297) | FString FGESHandler::AddLambdaListenerInt(FGESEventContext EventInfo, TF...
  function FString (line 309) | FString FGESHandler::AddLambdaListenerBool(FGESEventContext EventInfo, T...
  function FString (line 996) | FString FGESHandler::Key(const FString& Domain, const FString& Event)

FILE: Source/GlobalEventSystem/Private/GlobalEventSystemBPLibrary.cpp
  function FString (line 327) | FString UGlobalEventSystemBPLibrary::Conv_PropToString(const FGESWildcar...

FILE: Source/GlobalEventSystem/Public/GESBaseReceiverComponent.h
  function GLOBALEVENTSYSTEM_API (line 12) | GLOBALEVENTSYSTEM_API UGESBaseReceiverComponent : public UActorComponent

FILE: Source/GlobalEventSystem/Public/GESDataTypes.h
  function FGESGlobalOptions (line 9) | USTRUCT(BlueprintType)
  function FGESNameBind (line 30) | USTRUCT(BlueprintType)
  type FGESEventContext (line 60) | struct FGESEventContext
  function FGESWildcardProperty (line 107) | USTRUCT(BlueprintType)

FILE: Source/GlobalEventSystem/Public/GESHandler.h
  function class (line 21) | class GLOBALEVENTSYSTEM_API FGESHandler

FILE: Source/GlobalEventSystem/Public/GESHandlerDataTypes.h
  type FGESPinnedData (line 6) | struct FGESPinnedData
  type FGESDynamicArg (line 27) | struct FGESDynamicArg
  type FGESMinimalEventListener (line 33) | struct FGESMinimalEventListener
  function FGESMinimalEventListener (line 45) | struct FGESEventListener : FGESMinimalEventListener
  type FGESEventListenerWithContext (line 65) | struct FGESEventListenerWithContext
  function FGESEmitContext (line 87) | struct FGESEvent : FGESEmitContext
  function FGESEmitContext (line 99) | struct FGESPropertyEmitContext : FGESEmitContext

FILE: Source/GlobalEventSystem/Public/GESWorldListenerActor.h
  function GLOBALEVENTSYSTEM_API (line 14) | GLOBALEVENTSYSTEM_API AGESWorldListenerActor : public AActor

FILE: Source/GlobalEventSystem/Public/GlobalEventSystem.h
  function class (line 7) | class FGlobalEventSystemModule : public IModuleInterface

FILE: Source/GlobalEventSystem/Public/GlobalEventSystemBPLibrary.h
  function GLOBALEVENTSYSTEM_API (line 14) | GLOBALEVENTSYSTEM_API UGlobalEventSystemBPLibrary : public UBlueprintFun...
Condensed preview — 30 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (110K chars).
[
  {
    "path": ".gitignore",
    "chars": 990,
    "preview": "# Visual Studio 2015 user specific files\n.vs/\n\n# Visual Studio 2015 database file\n*.VC.db\n\n# Compiled Object files\n*.slo"
  },
  {
    "path": "Content/Scripts/ges/gesWrapper.js",
    "chars": 2676,
    "preview": "const uclass = require('uclass')().bind(this, global);\r\n\r\n/** \r\nWrapper class to enable some passthrough ges binding.\r\nA"
  },
  {
    "path": "GlobalEventSystem.uplugin",
    "chars": 588,
    "preview": "{\n\t\"FileVersion\": 3,\n\t\"Version\": 1,\n\t\"VersionName\": \"0.15.1\",\n\t\"FriendlyName\": \"GlobalEventSystem\",\n\t\"Description\": \"Loo"
  },
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2019 Jan Kaniewski\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 18908,
    "preview": "# GlobalEventSystem-Unreal\nA loosely coupled internal global event system (GES) plugin for the Unreal Engine. Aims to so"
  },
  {
    "path": "Source/GlobalEventSystem/GlobalEventSystem.Build.cs",
    "chars": 1166,
    "preview": "// Some copyright should be here...\n\nusing UnrealBuildTool;\n\npublic class GlobalEventSystem : ModuleRules\n{\n\tpublic Glob"
  },
  {
    "path": "Source/GlobalEventSystem/Private/GESBaseReceiverComponent.cpp",
    "chars": 2036,
    "preview": "#include \"GESBaseReceiverComponent.h\"\n#include \"GlobalEventSystemBPLibrary.h\"\n\nUGESBaseReceiverComponent::UGESBaseReceiv"
  },
  {
    "path": "Source/GlobalEventSystem/Private/GESDataTypes.cpp",
    "chars": 26,
    "preview": "#include \"GESDataTypes.h\"\n"
  },
  {
    "path": "Source/GlobalEventSystem/Private/GESHandler.cpp",
    "chars": 29819,
    "preview": "#include \"GESHandler.h\"\n#include \"GlobalEventSystemBPLibrary.h\"\n#include \"Engine/World.h\"\n\nTSharedPtr<FGESHandler> FGESH"
  },
  {
    "path": "Source/GlobalEventSystem/Private/GESHandlerDataTypes.cpp",
    "chars": 2290,
    "preview": "#include \"GESHandlerDataTypes.h\"\n\nvoid FGESPinnedData::CopyPropertyToPinnedBuffer()\n{\n\t//Copy this property data to temp"
  },
  {
    "path": "Source/GlobalEventSystem/Private/GESWorldListenerActor.cpp",
    "chars": 594,
    "preview": "// Copyright 2019-current Getnamo. All Rights Reserved\n\n\n#include \"GESWorldListenerActor.h\"\n\n// Sets default values\nAGES"
  },
  {
    "path": "Source/GlobalEventSystem/Private/GlobalEventSystem.cpp",
    "chars": 670,
    "preview": "// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.\n\n#include \"GlobalEventSystem.h\"\n\n#if WITH_EDITOR\n#include \""
  },
  {
    "path": "Source/GlobalEventSystem/Private/GlobalEventSystemBPLibrary.cpp",
    "chars": 13965,
    "preview": "// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.\n\n#include \"GlobalEventSystemBPLibrary.h\"\n#include \"GlobalEv"
  },
  {
    "path": "Source/GlobalEventSystem/Public/GESBaseReceiverComponent.h",
    "chars": 2019,
    "preview": "// Copyright 2019-current Getnamo. All Rights Reserved\n\n#pragma once\n\n#include \"Components/ActorComponent.h\"\n#include \"G"
  },
  {
    "path": "Source/GlobalEventSystem/Public/GESDataTypes.h",
    "chars": 3257,
    "preview": "#pragma once\n\n#include \"CoreMinimal.h\"\n#include \"GESDataTypes.generated.h\"\n\n/** \n* Global options for GESHandler. Used i"
  },
  {
    "path": "Source/GlobalEventSystem/Public/GESHandler.h",
    "chars": 5996,
    "preview": "#pragma once\n#include \"UObject/Object.h\"\n#include \"UObject/UnrealType.h\"\n#include \"GESWorldListenerActor.h\"\n#include \"GE"
  },
  {
    "path": "Source/GlobalEventSystem/Public/GESHandlerDataTypes.h",
    "chars": 2496,
    "preview": "#pragma once\n\n#include \"GESDataTypes.h\"\n\n/** Struct to hold pinned property data */\nstruct FGESPinnedData\n{\n\tFProperty* "
  },
  {
    "path": "Source/GlobalEventSystem/Public/GESWorldListenerActor.h",
    "chars": 801,
    "preview": "// Copyright 2019-current Getnamo. All Rights Reserved\n\n#pragma once\n\n#include \"CoreMinimal.h\"\n#include \"GameFramework/A"
  },
  {
    "path": "Source/GlobalEventSystem/Public/GlobalEventSystem.h",
    "chars": 371,
    "preview": "// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.\n\n#pragma once\n\n#include \"Modules/ModuleManager.h\"\n\nclass FG"
  },
  {
    "path": "Source/GlobalEventSystem/Public/GlobalEventSystemBPLibrary.h",
    "chars": 11671,
    "preview": "// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.\n\n#pragma once\n\n#include \"Kismet/BlueprintFunctionLibrary.h\""
  }
]

// ... and 10 more files (download for full content)

About this extraction

This page contains the full source code of the getnamo/GlobalEventSystem-Unreal GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 30 files (99.0 KB), approximately 26.1k tokens, and a symbol index with 43 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!