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.
[](https://github.com/getnamo/GlobalEventSystem-Unreal/releases)
[](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.

#### ```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.

#### ```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.

### 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).

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_).

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.

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

## 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

or optionally unbind individual events

## 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.

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

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?

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.

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

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.

#### Customizing your own receiver
Start with adding a new blueprint with _GESBaseReceiverComponent_ base class

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

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.

## 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);
};
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
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.