Compare commits
9 Commits
541f89b0b2
...
60596f2772
| Author | SHA1 | Date | |
|---|---|---|---|
| 60596f2772 | |||
| 648386cd73 | |||
| 56994b3927 | |||
| 8c0623b397 | |||
| dab9990b44 | |||
| fc1078f20c | |||
| 580e7cf72b | |||
| 367ac0bab7 | |||
| 4ae803b4f2 |
93
.emmyrc.json
93
.emmyrc.json
@ -1,93 +0,0 @@
|
||||
{
|
||||
"completion": {
|
||||
"enable": true,
|
||||
"autoRequire": true,
|
||||
"autoRequireFunction": "require",
|
||||
"autoRequireNamingConvention": "keep",
|
||||
"autoRequireSeparator": ".",
|
||||
"callSnippet": false,
|
||||
"postfix": "@",
|
||||
"baseFunctionIncludesName": true
|
||||
},
|
||||
"diagnostics": {
|
||||
"disable": [
|
||||
"unnecessary-if"
|
||||
],
|
||||
"enable": true,
|
||||
"globals": [],
|
||||
"globalsRegex": [],
|
||||
"severity": {},
|
||||
"enables": [],
|
||||
"diagnosticInterval": 500
|
||||
},
|
||||
"signature": {
|
||||
"detailSignatureHelper": true
|
||||
},
|
||||
"hint": {
|
||||
"enable": true,
|
||||
"paramHint": true,
|
||||
"indexHint": true,
|
||||
"localHint": true,
|
||||
"overrideHint": true,
|
||||
"metaCallHint": true
|
||||
},
|
||||
"runtime": {
|
||||
"version": "LuaLatest",
|
||||
"requireLikeFunction": [],
|
||||
"frameworkVersions": [],
|
||||
"extensions": [],
|
||||
"requirePattern": [],
|
||||
"classDefaultCall": {
|
||||
"functionName": "",
|
||||
"forceNonColon": false,
|
||||
"forceReturnSelf": false
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"ignoreDir": [],
|
||||
"ignoreGlobs": [],
|
||||
"library": [],
|
||||
"workspaceRoots": [],
|
||||
"preloadFileSize": 0,
|
||||
"encoding": "utf-8",
|
||||
"moduleMap": [],
|
||||
"reindexDuration": 5000,
|
||||
"enableReindex": false
|
||||
},
|
||||
"resource": {
|
||||
"paths": []
|
||||
},
|
||||
"codeLens": {
|
||||
"enable": true
|
||||
},
|
||||
"strict": {
|
||||
"requirePath": false,
|
||||
"typeCall": false,
|
||||
"arrayIndex": true,
|
||||
"metaOverrideFileDefine": true,
|
||||
"docBaseConstMatchBaseType": true
|
||||
},
|
||||
"semanticTokens": {
|
||||
"enable": true
|
||||
},
|
||||
"references": {
|
||||
"enable": true,
|
||||
"fuzzySearch": true,
|
||||
"shortStringSearch": false
|
||||
},
|
||||
"hover": {
|
||||
"enable": true
|
||||
},
|
||||
"documentColor": {
|
||||
"enable": true
|
||||
},
|
||||
"codeAction": {
|
||||
"insertSpace": false
|
||||
},
|
||||
"inlineValues": {
|
||||
"enable": true
|
||||
},
|
||||
"doc": {
|
||||
"privateName": []
|
||||
}
|
||||
}
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@ -77,3 +77,15 @@ Plugins/**/Intermediate/*
|
||||
DerivedDataCache/*
|
||||
|
||||
BusyRabbit.uproject.DotSettings.user
|
||||
|
||||
Plugins/SpinePlugin/Script/SpinePlugin.Glue
|
||||
Script/ManagedBusyRabbit/bin
|
||||
Script/ManagedBusyRabbit/obj
|
||||
Script/ManagedBusyRabbit/Properties
|
||||
Script/BusyRabbit.Glue/bin
|
||||
Script/BusyRabbit.Glue/BusyRabbit
|
||||
Script/BusyRabbit.Glue/obj
|
||||
Script/BusyRabbit.Glue/Properties
|
||||
Script/BusyRabbit.Glue/AssetIds.cs
|
||||
Script/BusyRabbit.Glue/GameplayTags.cs
|
||||
Script/BusyRabbit.Glue/TraceChannel.cs
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"FileVersion": 3,
|
||||
"EngineAssociation": "5.4",
|
||||
"EngineAssociation": "5.6",
|
||||
"Category": "",
|
||||
"Description": "",
|
||||
"Modules": [
|
||||
@ -9,7 +9,6 @@
|
||||
"Type": "Runtime",
|
||||
"LoadingPhase": "Default",
|
||||
"AdditionalDependencies": [
|
||||
"slua_unreal",
|
||||
"Engine",
|
||||
"CoreUObject",
|
||||
"GameplayAbilities",
|
||||
|
||||
@ -5,13 +5,24 @@ ClearInvalidTags=False
|
||||
AllowEditorTagUnloading=True
|
||||
AllowGameTagUnloading=False
|
||||
FastReplication=False
|
||||
bDynamicReplication=False
|
||||
InvalidTagCharacters="\"\',"
|
||||
NumBitsForContainerSize=6
|
||||
NetIndexFirstBitSegment=16
|
||||
+GameplayTagList=(Tag="Ability.Block.UltimatePlaying",DevComment="大招正在释放中")
|
||||
+GameplayTagList=(Tag="Ability.Common",DevComment="通用技能")
|
||||
+GameplayTagList=(Tag="Ability.Common.Move",DevComment="移动技能")
|
||||
+GameplayTagList=(Tag="Ability.Flags.Cooldown",DevComment="技能冷却")
|
||||
+GameplayTagList=(Tag="Ability.Flags.Recast",DevComment="可以再次释放技能的标记")
|
||||
+GameplayTagList=(Tag="Ability.Fox",DevComment="狐狸技能标签")
|
||||
+GameplayTagList=(Tag="Ability.Fox.CastTag",DevComment="可以释放技能的标签")
|
||||
+GameplayTagList=(Tag="Ability.Fox.CastTag.Ultimate1",DevComment="可以使用大招第一阶段")
|
||||
+GameplayTagList=(Tag="Ability.Fox.CastTag.Ultimate2",DevComment="可以使用二阶大招")
|
||||
+GameplayTagList=(Tag="Ability.Fox.CastTag.Ultimate3",DevComment="可以使用第三阶段大招")
|
||||
+GameplayTagList=(Tag="Ability.Fox.StateTag",DevComment="状态标签")
|
||||
+GameplayTagList=(Tag="Ability.Fox.StateTag.Ultimate1",DevComment="正在使用第一阶段大招")
|
||||
+GameplayTagList=(Tag="Ability.Fox.StateTag.Ultimate2",DevComment="正在使用第二阶段大招")
|
||||
+GameplayTagList=(Tag="Ability.Fox.StateTag.Ultimate3",DevComment="正在使用第三阶段大招")
|
||||
+GameplayTagList=(Tag="Ability.Fox.UltimateStage1",DevComment="一阶大招")
|
||||
+GameplayTagList=(Tag="Ability.Fox.UltimateStage2",DevComment="二阶大招就绪标签")
|
||||
+GameplayTagList=(Tag="Ability.Fox.UltimateStage3",DevComment="三阶大招就绪")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/Gas/Ability/Role/Common/GA_MoveAbility.uasset
Normal file
BIN
Content/Gas/Ability/Role/Common/GA_MoveAbility.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,38 +0,0 @@
|
||||
---@enum EBusyRoleState
|
||||
local EBusyRoleState = {
|
||||
BonfireIdle = 0,
|
||||
Searching = 1,
|
||||
Picking = 2,
|
||||
PickFinished = 3,
|
||||
BackBonfire = 4
|
||||
}
|
||||
|
||||
---@enum ERoleMoveDirection
|
||||
local ERoleMoveDirection = {
|
||||
Move_Right = 0,
|
||||
Move_Left = 1,
|
||||
Move_All_Cnt = 2
|
||||
};
|
||||
|
||||
---@enum EBusyItemEffectType
|
||||
local EBusyItemEffectType = {
|
||||
Health = 0,
|
||||
Hunger = 1,
|
||||
Speed = 2,
|
||||
};
|
||||
|
||||
---@enum EBusyAnimationPhase
|
||||
local EBusyAnimationPhase = {
|
||||
PrepareCast = 0,
|
||||
Casting = 1,
|
||||
PostCast = 2,
|
||||
};
|
||||
|
||||
---@enum EWidgetLayoutType
|
||||
local EWidgetLayoutType = {
|
||||
MainLayer = 0,
|
||||
PopupLayer = 1,
|
||||
FloatLayer = 2,
|
||||
TopLayer = 3,
|
||||
LayerTypeMax = 4
|
||||
};
|
||||
@ -1,4 +0,0 @@
|
||||
--- @class BusyGameplayLibrary
|
||||
--- @field K2_GetWorld fun(obj:table):table
|
||||
--- @field RequestGameplayTag fun(TagName:string):GameplayTag
|
||||
local BusyGameplayLibrary = {}
|
||||
@ -1,42 +0,0 @@
|
||||
-- 这个返回值固定返回的是string,我希望当我使用import("ERoleState")时,它返回的是ERoleState的注解,或者是ERoleState.lua这个文件返回值的注解
|
||||
-- ---自定义导入函数,功能类似require但支持额外特性
|
||||
-- ---@generic T
|
||||
-- ---@param modulePath T 模块路径或预加载的模块
|
||||
-- ---@param hotReload? boolean 是否启用热重载
|
||||
-- ---@return T 返回加载的模块
|
||||
-- function import(modulePath, hotReload) end
|
||||
|
||||
|
||||
---自定义模块导入函数,支持类型感知的模块加载
|
||||
---@generic T : string -- 限定modulePath为字符串类型
|
||||
---@param modulePath `T` 模块路径(如"ERoleState")
|
||||
---@param hotReload? boolean 是否启用热重载
|
||||
---@return T 返回对应模块的类型
|
||||
---@error 当模块加载失败时抛出错误
|
||||
function import(modulePath, hotReload) end
|
||||
|
||||
|
||||
function Class(a, b, c) end
|
||||
|
||||
---@class slua
|
||||
slua = {
|
||||
createDelegate = function(func) end
|
||||
}
|
||||
|
||||
--- @class KismetSystemLibrary
|
||||
KismetSystemLibrary = {
|
||||
K2_ClearTimerHandle = function() end
|
||||
}
|
||||
|
||||
|
||||
--- @class GameplayTag
|
||||
local GameplayTag
|
||||
|
||||
--- @class GameplayStatics
|
||||
--- @field GetPlayerController fun(uobj:table,idx:number):table
|
||||
--- @field GetGameState fun(uobj:table):table
|
||||
local GameplayStatics
|
||||
|
||||
--- @class AbilitySystemBlueprintLibrary
|
||||
--- @field AssignTagSetByCallerMagnitude fun(SpecHandle:table, DataTag:GameplayTag, Magnitude:number)
|
||||
local AbilitySystemBlueprintLibrary
|
||||
@ -1,8 +0,0 @@
|
||||
---@enum ESlateVisibility
|
||||
local ESlateVisibility = {
|
||||
Visible = 0,
|
||||
Collapsed = 1,
|
||||
Hidden = 2,
|
||||
HitTestInvisible = 3,
|
||||
SelfHitTestInvisible = 4
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,222 +0,0 @@
|
||||
#ifndef LIBPDEBUG_H
|
||||
#define LIBPDEBUG_H
|
||||
|
||||
//1.使用源码编译,要打开宏USE_SOURCE_CODE. win下要设置LUA_INTEGER和lua版本号
|
||||
#define LUA_DEBUGGER_NAME "LuaPanda" //debugger's name in LuaDebug.lua
|
||||
#define HOOK_LIB_VERSION "3.2.0" //lib version
|
||||
//#define USE_SOURCE_CODE //using source code to build
|
||||
#if !defined(USE_SOURCE_CODE) && defined(_WIN32)
|
||||
#define LUA_INTEGER long long //set LUA_INTEGER. In 501 is ptrdiff_t. 503 can set longlong(64bit) or int(32bit)
|
||||
#define LUA_VERSION_NUM 503 //lua version used by WIN32 build lib. eg. 501,503
|
||||
#endif
|
||||
//setting end
|
||||
|
||||
#if !defined(USE_SOURCE_CODE) && defined(_WIN32)
|
||||
#include <Windows.h>
|
||||
#include <Tlhelp32.h>
|
||||
#else
|
||||
//2.如果lua源码是C++形式,注释掉下面extern "C"
|
||||
extern "C"{
|
||||
#include "lua.h"
|
||||
#include "lualib.h"
|
||||
#include "lauxlib.h"
|
||||
#include "luaconf.h"
|
||||
}
|
||||
#endif
|
||||
|
||||
//3.如果lua代码在命名空间中,要设置用户命名空间. 防止找不到lua方法
|
||||
//using namespace slua;
|
||||
|
||||
#ifdef USE_SOURCE_CODE
|
||||
extern "C" void pdebug_init(lua_State* L);
|
||||
#endif
|
||||
|
||||
#if !defined(USE_SOURCE_CODE) && defined(_WIN32)
|
||||
/*
|
||||
** Lua - An Extensible Extension Language
|
||||
** Lua.org, PUC-Rio, Brazil (http://www.lua.org)
|
||||
** See Copyright Notice at the end of this file
|
||||
*/
|
||||
#if LUA_VERSION_NUM == 501
|
||||
#define lua_getglobal(L,s) lua_getfield(L, LUA_GLOBALSINDEX, (s))
|
||||
#endif
|
||||
|
||||
#define LUA_TNONE (-1)
|
||||
#define LUA_TNIL 0
|
||||
#define LUA_TBOOLEAN 1
|
||||
#define LUA_TLIGHTUSERDATA 2
|
||||
#define LUA_TNUMBER 3
|
||||
#define LUA_TSTRING 4
|
||||
#define LUA_TTABLE 5
|
||||
#define LUA_TFUNCTION 6
|
||||
#define LUA_TUSERDATA 7
|
||||
#define LUA_TTHREAD 8
|
||||
#define LUA_NUMBER double
|
||||
#define LUA_REGISTRYINDEX (-10000)
|
||||
#define LUA_ENVIRONINDEX (-10001)
|
||||
#define LUA_GLOBALSINDEX (-10002)
|
||||
#define lua_upvalueindex(i) (LUA_GLOBALSINDEX-(i))
|
||||
#define LUA_IDSIZE 60
|
||||
#define LUA_HOOKCALL 0
|
||||
#define LUA_HOOKRET 1
|
||||
#define LUA_HOOKLINE 2
|
||||
#define LUA_HOOKCOUNT 3
|
||||
#define LUA_HOOKTAILRET 4
|
||||
#define LUA_MASKCALL (1 << LUA_HOOKCALL)
|
||||
#define LUA_MASKRET (1 << LUA_HOOKRET)
|
||||
#define LUA_MASKLINE (1 << LUA_HOOKLINE)
|
||||
#define LUA_MASKCOUNT (1 << LUA_HOOKCOUNT)
|
||||
#define luaL_checkstring(L,n) (luaL_checklstring(L, (n), NULL))
|
||||
#define lua_tostring(L,i) lua_tolstring(L, (i), NULL)
|
||||
#define lua_istable(L,n) (lua_type(L, (n)) == LUA_TTABLE)
|
||||
#define lua_isfunction(L,n) (lua_type(L, (n)) == LUA_TFUNCTION)
|
||||
#define lua_pop(L,n) lua_settop(L, -(n)-1)
|
||||
#define lua_newtable(L) lua_createtable(L, 0, 0)
|
||||
|
||||
struct lua_State;
|
||||
struct lua_Debug {
|
||||
int event;
|
||||
const char *name; /* (n) */
|
||||
const char *namewhat; /* (n) `global', `local', `field', `method' */
|
||||
const char *what; /* (S) `Lua', `C', `main', `tail' */
|
||||
const char *source; /* (S) */
|
||||
int currentline; /* (l) */
|
||||
int nups; /* (u) number of upvalues */
|
||||
int linedefined; /* (S) */
|
||||
int lastlinedefined; /* (S) */
|
||||
char short_src[LUA_IDSIZE]; /* (S) */
|
||||
/* private part */
|
||||
int i_ci; /* active function */
|
||||
};
|
||||
|
||||
typedef LUA_INTEGER lua_Integer;
|
||||
typedef LUA_NUMBER lua_Number;
|
||||
typedef int (*lua_CFunction) (lua_State *L);
|
||||
typedef struct luaL_Reg {
|
||||
const char *name;
|
||||
lua_CFunction func;
|
||||
} luaL_Reg;
|
||||
|
||||
#define LUA_KCONTEXT ptrdiff_t
|
||||
typedef LUA_KCONTEXT lua_KContext;
|
||||
|
||||
//lua function
|
||||
typedef lua_Integer(*luaDLL_checkinteger) (lua_State *L, int numArg);
|
||||
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
|
||||
typedef int (*lua_KFunction) (lua_State *L, int status, lua_KContext ctx);
|
||||
typedef const lua_Number *(*luaDLL_version)(lua_State *L);
|
||||
typedef void (*luaLDLL_register)(lua_State *L, const char *libname, const luaL_Reg *l);
|
||||
typedef int (*luaDLL_gettop)(lua_State *L);
|
||||
typedef const char *(*luaDLL_pushstring)(lua_State *L, const char *s);
|
||||
typedef int (*luaDLL_settop)(lua_State *L, int idx);
|
||||
typedef int (*luaDLL_tointeger)(lua_State *L, int idx);
|
||||
typedef int (*luaDLL_next)(lua_State *L, int idx);
|
||||
typedef int (*luaDLL_pcall)(lua_State *L, int nargs, int nresults, int errfunc);
|
||||
typedef void (*luaDLL_pushnil)(lua_State *L);
|
||||
typedef void (*luaDLL_getfield)(lua_State *L, int idx, const char *k);
|
||||
typedef int (*luaDLL_getinfo)(lua_State *L, const char *what, void *ar);
|
||||
typedef void (*luaDLL_pushinteger) (lua_State *L, lua_Integer n);
|
||||
#if LUA_VERSION_NUM == 501
|
||||
typedef int(*luaDLL_sethook)(lua_State *L, void* func, int mask, int count);
|
||||
#else
|
||||
typedef void (*luaDLL_sethook)(lua_State *L, lua_Hook f, int mask, int count);
|
||||
#endif
|
||||
typedef void (*luaDLL_pushnumber)(lua_State *L, lua_Number n);
|
||||
typedef lua_Number (*luaDLL_checknumber)(lua_State *L, int narg);
|
||||
typedef const char *(*luaDLL_checklstring)(lua_State *L, int narg, size_t *len);
|
||||
typedef const char *(*luaDLL_tolstring)(lua_State *L, int idx, size_t *len);
|
||||
typedef int (*luaDLL_type)(lua_State *L, int idx);
|
||||
//5.3
|
||||
typedef void (*luaDLL_createtable)(lua_State *L, int narray, int nrec);
|
||||
typedef void (*luaDLL_setfuncs)(lua_State *L, const luaL_Reg *l, int nup);
|
||||
typedef lua_Integer(*luaDLL_tointegerx)(lua_State *L, int idx, int *pisnum);
|
||||
typedef int (*luaDLL_getglobal)(lua_State *L, const char *name);
|
||||
typedef int (*luaDLL_pcallk)(lua_State *L, int nargs, int nresults, int msgh, lua_KContext ctx, lua_KFunction k);
|
||||
typedef int (*luaDLL_toboolean)(lua_State *L, int index);
|
||||
|
||||
luaDLL_checkinteger luaL_checkinteger;
|
||||
luaDLL_version lua_version;
|
||||
luaDLL_gettop lua_gettop;
|
||||
luaDLL_pushstring lua_pushstring;
|
||||
luaLDLL_register luaL_register;
|
||||
luaDLL_settop lua_settop;
|
||||
luaDLL_pcall lua_pcall;
|
||||
luaDLL_pushnumber lua_pushnumber;
|
||||
luaDLL_checklstring luaL_checklstring;
|
||||
luaDLL_tointeger lua_tointeger;
|
||||
luaDLL_pushnil lua_pushnil;
|
||||
luaDLL_getfield lua_getfield;
|
||||
luaDLL_next lua_next;
|
||||
luaDLL_getinfo lua_getinfo;
|
||||
luaDLL_sethook lua_sethook;
|
||||
luaDLL_checknumber luaL_checknumber;
|
||||
luaDLL_type lua_type;
|
||||
luaDLL_tolstring lua_tolstring;
|
||||
luaDLL_pushinteger lua_pushinteger;
|
||||
luaDLL_toboolean lua_toboolean;
|
||||
//
|
||||
HMODULE hInstLibrary;
|
||||
|
||||
//slua-ue header
|
||||
#if LUA_VERSION_NUM > 501
|
||||
//5.3
|
||||
luaDLL_createtable lua_createtable;
|
||||
luaDLL_setfuncs luaL_setfuncs;
|
||||
luaDLL_tointegerx lua_tointegerx;
|
||||
luaDLL_getglobal lua_getglobal;
|
||||
luaDLL_pcallk lua_pcallk;
|
||||
#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL)
|
||||
#define lua_tointeger(L,i) lua_tointegerx(L,(i),NULL);
|
||||
|
||||
#define PURE_API =0
|
||||
namespace slua {
|
||||
struct LuaInterface {
|
||||
virtual const lua_Number *lua_version(lua_State *L) PURE_API;
|
||||
virtual const char *lua_pushstring(lua_State *L, const char *s) PURE_API;
|
||||
virtual int lua_gettop(lua_State *L) PURE_API;
|
||||
virtual void lua_settop(lua_State *L, int index) PURE_API;
|
||||
virtual int lua_pcallk(lua_State *L, int nargs, int nresults, int msgh, lua_KContext ctx, lua_KFunction k) PURE_API;
|
||||
virtual void lua_pushnumber(lua_State *L, lua_Number n) PURE_API;
|
||||
virtual const char *luaL_checklstring(lua_State *L, int arg, size_t *l) PURE_API;
|
||||
virtual const char *lua_tolstring(lua_State *L, int index, size_t *len) PURE_API;
|
||||
virtual int lua_type(lua_State *L, int index) PURE_API;
|
||||
virtual lua_Integer lua_tointegerx(lua_State *L, int index, int *isnum) PURE_API;
|
||||
virtual void lua_pushnil(lua_State *L) PURE_API;
|
||||
virtual int lua_getfield(lua_State *L, int index, const char *k) PURE_API;
|
||||
virtual int lua_next(lua_State *L, int index) PURE_API;
|
||||
virtual int lua_getinfo(lua_State *L, const char *what, lua_Debug *ar) PURE_API;
|
||||
virtual void lua_sethook(lua_State *L, lua_Hook f, int mask, int count) PURE_API;
|
||||
virtual lua_Number luaL_checknumber(lua_State *L, int arg) PURE_API;
|
||||
virtual void lua_createtable(lua_State *L, int narr, int nrec) PURE_API;
|
||||
virtual void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) PURE_API;
|
||||
virtual int lua_getglobal(lua_State *L, const char *name) PURE_API;
|
||||
virtual int lua_toboolean(lua_State *L, int index) PURE_API;
|
||||
};
|
||||
}
|
||||
typedef slua::LuaInterface* (*dll_GetLuaInterface)();
|
||||
dll_GetLuaInterface getInter;
|
||||
#endif //LUA_VERSION_NUM > 501
|
||||
#endif //_WIN32
|
||||
#endif //LIBPDEBUG_H
|
||||
/******************************************************************************
|
||||
* Copyright (C) 1994-2008 Lua.org, PUC-Rio. All rights reserved.
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
@ -1,4 +0,0 @@
|
||||
local MoveAbility = {}
|
||||
|
||||
|
||||
return Class(nil, nil, MoveAbility)
|
||||
@ -1,196 +0,0 @@
|
||||
local GameplayStatics = import("GameplayStatics")
|
||||
local AbilitySystemBlueprintLibrary = import("AbilitySystemBlueprintLibrary")
|
||||
local BusyGameplayLibrary = import("BusyGameplayLibrary")
|
||||
|
||||
--- @class FoxUltimate
|
||||
--- @field RecastWindow number
|
||||
local FoxUltimate = {}
|
||||
|
||||
|
||||
function FoxUltimate:ctor()
|
||||
self.ultimate_phase = 1 -- 大招阶段
|
||||
self.active_recast_handle = nil
|
||||
self.active_accelerate_handle = nil
|
||||
|
||||
self.overlap_delegate_handle = nil
|
||||
self.tag_add_or_remove_delegate = nil
|
||||
end
|
||||
|
||||
|
||||
function FoxUltimate:K2_ActivateAbilityFromEvent(EventData)
|
||||
print("FoxUltimate:K2_ActivateAbilityFromEvent", self.ultimate_phase)
|
||||
|
||||
if not self:K2_CommitAbilityCost(false) then
|
||||
return self:K2_EndAbility()
|
||||
end
|
||||
|
||||
local owner = self:GetOwningActorFromActorInfo()
|
||||
self.movement = owner.MovementComponent
|
||||
self.animation = owner.SpineAnimationComponent
|
||||
self.asc = AbilitySystemBlueprintLibrary.GetAbilitySystemComponent(owner)
|
||||
self.recast_tag = BusyGameplayLibrary.RequestGameplayTag("Ability.Flags.Recast")
|
||||
self.owner = owner
|
||||
|
||||
|
||||
local asc = self.asc
|
||||
|
||||
if self.tag_add_or_remove_delegate == nil then
|
||||
self.tag_add_or_remove_delegate = slua.createDelegate(function(tag, is_add)
|
||||
if is_add == 0 and not self.bIsActive then
|
||||
self.ultimate_phase = 1
|
||||
self:K2_CommitAbilityCooldown(false, false)
|
||||
end
|
||||
end)
|
||||
owner:BindGameplayTagAddOrRemove(self.recast_tag, self.tag_add_or_remove_delegate)
|
||||
end
|
||||
|
||||
if self.ultimate_phase == 1 or asc:HasMatchingGameplayTag(self.recast_tag) then
|
||||
self:TriggerUltimate(asc)
|
||||
else
|
||||
self:K2_EndAbility()
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
function FoxUltimate:TriggerUltimate(asc)
|
||||
if self.ultimate_phase == 1 then
|
||||
-- 第一次释放大招,添加一个可以再次释放的Tag
|
||||
local _, recast_effect = self:GetAbilityEffectSpecHandle("Recast", asc, 1, nil)
|
||||
self.active_recast_handle = asc:BP_ApplyGameplayEffectSpecToSelf(recast_effect)
|
||||
elseif self.ultimate_phase == 2 then
|
||||
-- 第二次激活,移除可重复释放的tag
|
||||
self.asc:RemoveActiveGameplayEffect(self.active_recast_handle, -1)
|
||||
elseif self.ultimate_phase == 3 then
|
||||
self.asc:RemoveActiveGameplayEffect(self.active_recast_handle, -1)
|
||||
end
|
||||
|
||||
-- 播放动画,并监听动画完成的事件
|
||||
self:PlayAnimation()
|
||||
end
|
||||
|
||||
|
||||
function FoxUltimate:GetAnimationName(direction)
|
||||
local animation_name_prefix
|
||||
if direction.X >= 0 then
|
||||
animation_name_prefix = "Ultimate/Right/"
|
||||
else
|
||||
animation_name_prefix = "Ultimate/Left/"
|
||||
end
|
||||
|
||||
if self.ultimate_phase == 1 then
|
||||
return animation_name_prefix .. "UltimateStage1"
|
||||
elseif self.ultimate_phase == 2 then
|
||||
return animation_name_prefix .. "UltimateStage2"
|
||||
elseif self.ultimate_phase == 3 then
|
||||
return animation_name_prefix .. "UltimateStage3"
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
function FoxUltimate:K2_OnEndAbility(bWasCancelled)
|
||||
print("FoxUltimate:K2_OnEndAbility")
|
||||
end
|
||||
|
||||
|
||||
function FoxUltimate:GetSprintSpeedFactor()
|
||||
if self.ultimate_phase == 1 then
|
||||
return 2.5
|
||||
elseif self.ultimate_phase == 2 then
|
||||
return 2.8
|
||||
elseif self.ultimate_phase == 3 then
|
||||
return 3.0
|
||||
end
|
||||
end
|
||||
|
||||
function FoxUltimate:PlayAnimation()
|
||||
|
||||
local animation = self.animation
|
||||
local PC = GameplayStatics.GetPlayerController(self, 0)
|
||||
|
||||
local result, ability_direction = PC:GetCursorDirection(nil) -- 获取技能的朝向
|
||||
if not result then
|
||||
print("FoxUltimate:TriggerPrimaryPhase can't find direction")
|
||||
return
|
||||
end
|
||||
local anim_name = self:GetAnimationName(ability_direction) -- 获取技能动画名称
|
||||
if not anim_name then
|
||||
print("FoxUltimate:TriggerPrimaryPhase can't get animation", self.ultimate_phase)
|
||||
return
|
||||
end
|
||||
|
||||
local anim_entry = animation:SetAnimation(0, anim_name, false)
|
||||
anim_entry.AnimationComplete:Add(function(entry) self:OnAnimationComplete(entry) end)
|
||||
anim_entry.AnimationEvent:Add(function(entry, event) self:OnAnimationEvent(entry, event) end)
|
||||
|
||||
local anim_total_time = anim_entry:GetAnimationEnd() -- 获取技能动画时长
|
||||
|
||||
-- 附加加速buff
|
||||
self:MakeAccelerate(self:GetSprintSpeedFactor(), anim_total_time)
|
||||
self.movement:ActivateSprint(ability_direction, anim_total_time)
|
||||
end
|
||||
|
||||
|
||||
function FoxUltimate:MakeAccelerate(speed_factor, total_time)
|
||||
local _, accelerate_effect = self:GetAbilityEffectSpecHandle("Accelerate", self.asc, 1, nil)
|
||||
AbilitySystemBlueprintLibrary.AssignTagSetByCallerMagnitude(
|
||||
accelerate_effect,
|
||||
BusyGameplayLibrary.RequestGameplayTag("Effect.Factor"),
|
||||
speed_factor
|
||||
)
|
||||
|
||||
AbilitySystemBlueprintLibrary.AssignTagSetByCallerMagnitude(
|
||||
accelerate_effect,
|
||||
BusyGameplayLibrary.RequestGameplayTag("Effect.Duration"),
|
||||
total_time
|
||||
)
|
||||
return self.asc:BP_ApplyGameplayEffectSpecToSelf(accelerate_effect)
|
||||
end
|
||||
|
||||
|
||||
function FoxUltimate:OnAnimationComplete(entry)
|
||||
local new_phase = self.ultimate_phase + 1
|
||||
if new_phase > 3 then
|
||||
self.ultimate_phase = 1
|
||||
else
|
||||
self.ultimate_phase = new_phase
|
||||
end
|
||||
print("FoxUltimate:OnAnimationComplete", self.ultimate_phase)
|
||||
|
||||
if not self.asc:HasMatchingGameplayTag(self.recast_tag) then
|
||||
self.ultimate_phase = 1
|
||||
self:K2_CommitAbilityCooldown(false, false)
|
||||
end
|
||||
|
||||
entry.AnimationComplete:Clear()
|
||||
entry.AnimationEvent:Clear()
|
||||
self:K2_EndAbility()
|
||||
end
|
||||
|
||||
function FoxUltimate:OnAnimationEvent(entry, event)
|
||||
if event.Name == "OnSpeedChange" then
|
||||
self.active_accelerate_handle = self:MakeAccelerate(8.0, 0.5)
|
||||
elseif event.Name == "OnSpeedReset" then
|
||||
self.asc:RemoveActiveGameplayEffect(self.active_accelerate_handle, -1)
|
||||
elseif event.Name == "OnDamageBegin" then
|
||||
local collision = self.owner["TailCollision"]
|
||||
collision:SetCollisionEnabled(1)
|
||||
self.overlap_delegate_handle = collision.OnComponentBeginOverlap:Add(function()
|
||||
if not self.asc:HasMatchingGameplayTag(self.recast_tag) then
|
||||
local _, recast_effect = self:GetAbilityEffectSpecHandle("Recast", self.asc, 1, nil)
|
||||
self.active_recast_handle = self.asc:BP_ApplyGameplayEffectSpecToSelf(recast_effect)
|
||||
end
|
||||
end)
|
||||
elseif event.Name == "OnDamageEnd" then
|
||||
self.owner["TailCollision"]:SetCollisionEnabled(0)
|
||||
if self.overlap_delegate_handle ~= nil then
|
||||
self.owner["TailCollision"].OnComponentBeginOverlap:Remove(self.overlap_delegate_handle)
|
||||
self.overlap_delegate_handle = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
return Class(nil, nil, FoxUltimate)
|
||||
@ -1,4 +0,0 @@
|
||||
local RabbitUltimate = {}
|
||||
|
||||
|
||||
return Class(nil, nil, RabbitUltimate)
|
||||
@ -1,54 +0,0 @@
|
||||
local LevelFoxRole = {}
|
||||
local Vector2D = require("Utils.Vector2D")
|
||||
|
||||
function LevelFoxRole:ctor()
|
||||
|
||||
end
|
||||
|
||||
function LevelFoxRole:ReceiveBeginPlay()
|
||||
self["SpineAnimationComponent"]:SetAnimation(0, "Idle/Front", true)
|
||||
self.last_animation = "Idle/Front"
|
||||
|
||||
self["SpineBoneFollower"].Target = self
|
||||
self["SpineBoneFollower"].BoneName = "tail"
|
||||
self["SpineBoneFollower"].UseComponentTransform = true
|
||||
self["SpineBoneFollower"].UseScale = true
|
||||
|
||||
self["TailCollision"]:SetCollisionEnabled(0)
|
||||
end
|
||||
|
||||
|
||||
function LevelFoxRole:OnMoveDirectionChanged(InDirection)
|
||||
-- 运动组件更新方向响应函数
|
||||
local cur_animation
|
||||
if Vector2D.Equals(InDirection, Vector2D.Zero) then -- Idle的情况
|
||||
local forward_direction = self["MovementComponent"]:GetForwardDirection()
|
||||
if(forward_direction.Y >= 0) then
|
||||
cur_animation = "Idle/Front"
|
||||
else
|
||||
cur_animation = "Idle/Back"
|
||||
end
|
||||
else
|
||||
if(InDirection.Y >= 0) then
|
||||
cur_animation = "Move/Front"
|
||||
else
|
||||
cur_animation = "Move/Back"
|
||||
end
|
||||
end
|
||||
if cur_animation ~= self.last_animation then
|
||||
self["SpineAnimationComponent"]:SetAnimation(0, cur_animation, true)
|
||||
self.last_animation = cur_animation
|
||||
end
|
||||
|
||||
self["SpineAnimationComponent"]:SetTimeScale(1.0)
|
||||
end
|
||||
|
||||
|
||||
function LevelFoxRole:OnMove(location)
|
||||
-- 控制器移动相应函数
|
||||
self["MovementComponent"]:MoveTo(location)
|
||||
end
|
||||
|
||||
|
||||
|
||||
return Class(nil, nil, LevelFoxRole)
|
||||
@ -1,36 +0,0 @@
|
||||
local LevelRabbitRole = {}
|
||||
|
||||
function LevelRabbitRole:ctor()
|
||||
self.last_animation = "back/move"
|
||||
end
|
||||
|
||||
function LevelRabbitRole:ReceiveBeginPlay()
|
||||
self["SpineAnimationComponent"]:SetSkin("back/move")
|
||||
self["SpineAnimationComponent"]:SetAnimation(0, "animation", true)
|
||||
end
|
||||
|
||||
|
||||
function LevelRabbitRole:OnMove(location)
|
||||
-- 控制器移动相应函数
|
||||
self["MovementComponent"]:MoveTo(location)
|
||||
end
|
||||
|
||||
|
||||
function LevelRabbitRole:OnMoveDirectionChanged(InDirection)
|
||||
-- 运动组件更新方向响应函数
|
||||
local cur_animation
|
||||
if(InDirection.Y >= 0) then
|
||||
cur_animation = "front/move"
|
||||
else
|
||||
cur_animation = "back/move"
|
||||
end
|
||||
print("LevelRabbitRole:OnMoveDirectionChanged", cur_animation)
|
||||
if cur_animation ~= self.last_animation then
|
||||
self["SpineAnimationComponent"]:SetSkin(cur_animation)
|
||||
self["SpineAnimationComponent"]:SetSlotsToSetupPose()
|
||||
self.last_animation = cur_animation
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return Class(nil, nil, LevelRabbitRole)
|
||||
@ -1,4 +0,0 @@
|
||||
local Campsite = {}
|
||||
|
||||
|
||||
return Class(nil, nil, Campsite)
|
||||
@ -1,45 +0,0 @@
|
||||
local Vector = import("Vector")
|
||||
local GameplayStatics = import("GameplayStatics")
|
||||
local BusyGameplayLibrary = import("BusyGameplayLibrary")
|
||||
|
||||
|
||||
--[[****************************私有函数区域******************************]]--
|
||||
|
||||
--- 生成资源点
|
||||
local function GenerateResourcePoint()
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- 保留到以后做联机内容时拓展
|
||||
--- @class LevelGameMode
|
||||
--- @field GameMapActorClass table
|
||||
local LevelGameMode = {}
|
||||
|
||||
function LevelGameMode:ctor()
|
||||
self.map_actor = nil
|
||||
end
|
||||
|
||||
function LevelGameMode:ReceiveBeginPlay()
|
||||
print("LevelGameMode:ReceiveBeginPlay", self.GameMapActorClass)
|
||||
local world = BusyGameplayLibrary.K2_GetWorld(self)
|
||||
local game_state = GameplayStatics.GetGameState(self) --- @type LevelGameState
|
||||
|
||||
if self.GameMapActorClass then
|
||||
local map_actor = world:SpawnActor(self.GameMapActorClass, Vector(), nil, nil)
|
||||
game_state:SetGameMapActor(map_actor)
|
||||
end
|
||||
end
|
||||
|
||||
-- function LevelGameMode:K2_PostLogin(new_player_controller)
|
||||
-- local new_player_state = new_player_controller.PlayerState
|
||||
-- local role = new_player_state:CreateRoleRoster(new_player_controller)
|
||||
-- local new_pos = FVector()
|
||||
-- new_pos.X = 500
|
||||
-- new_pos.Y = 500
|
||||
-- new_pos.Z = 50
|
||||
-- role:K2_SetActorLocation(new_pos, true, nil, false)
|
||||
-- new_player_controller:Possess(role)
|
||||
-- end
|
||||
|
||||
return Class(nil, nil, LevelGameMode)
|
||||
@ -1,9 +0,0 @@
|
||||
--- @class LevelGameState
|
||||
local LevelGameState = {}
|
||||
|
||||
function LevelGameState:SetGameMapActor(game_map_actor)
|
||||
self.GameMapActor = game_map_actor
|
||||
end
|
||||
|
||||
|
||||
return Class(nil, nil, LevelGameState)
|
||||
@ -1,12 +0,0 @@
|
||||
local GameplayStatics = import("GameplayStatics")
|
||||
|
||||
local LevelPlayerState = {}
|
||||
|
||||
function LevelPlayerState:ReceiveBeginPlay()
|
||||
local pc = GameplayStatics.GetPlayerController(self, 0)
|
||||
local role = self:CreateRoleRoster(pc)
|
||||
print("LevelPlayerState:ReceiveBeginPlay", role, self:HasAuthority())
|
||||
pc:Possess(role)
|
||||
end
|
||||
|
||||
return Class(nil, nil, LevelPlayerState)
|
||||
Binary file not shown.
Binary file not shown.
2
Plugins/UnrealSharp/.gitattributes
vendored
Normal file
2
Plugins/UnrealSharp/.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
84
Plugins/UnrealSharp/.gitignore
vendored
Normal file
84
Plugins/UnrealSharp/.gitignore
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
# Visual Studio 2015 user specific files
|
||||
.vs/
|
||||
|
||||
# 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
|
||||
*.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/*
|
||||
|
||||
*.user
|
||||
|
||||
.idea/
|
||||
|
||||
bin/
|
||||
Generated/
|
||||
obj/
|
||||
|
||||
*.props
|
||||
!Directory.Packages.props
|
||||
117
Plugins/UnrealSharp/Config/Default.UnrealSharpTypes.json
Normal file
117
Plugins/UnrealSharp/Config/Default.UnrealSharpTypes.json
Normal file
@ -0,0 +1,117 @@
|
||||
{
|
||||
"Structs": {
|
||||
"CustomTypes": [
|
||||
"FInstancedStruct"
|
||||
],
|
||||
"BlittableTypes": [
|
||||
{
|
||||
"Name": "EStreamingSourcePriority"
|
||||
},
|
||||
{
|
||||
"Name": "ETriggerEvent"
|
||||
},
|
||||
{
|
||||
"Name": "FVector",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector"
|
||||
},
|
||||
{
|
||||
"Name": "FVector2D",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector2D"
|
||||
},
|
||||
{
|
||||
"Name": "FVector_NetQuantize",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector"
|
||||
},
|
||||
{
|
||||
"Name": "FVector_NetQuantize10",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector"
|
||||
},
|
||||
{
|
||||
"Name": "FVector_NetQuantize100",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector"
|
||||
},
|
||||
{
|
||||
"Name": "FVector_NetQuantizeNormal",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector"
|
||||
},
|
||||
{
|
||||
"Name": "FVector2f",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector2f"
|
||||
},
|
||||
{
|
||||
"Name": "FVector3f",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector3f"
|
||||
},
|
||||
{
|
||||
"Name": "FVector4f",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector4f"
|
||||
},
|
||||
{
|
||||
"Name": "FQuatf4",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FVector4f"
|
||||
},
|
||||
{
|
||||
"Name": "FRotator",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FRotator"
|
||||
},
|
||||
{
|
||||
"Name": "FMatrix44f",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FMatrix44f"
|
||||
},
|
||||
{
|
||||
"Name": "FTransform",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FTransform"
|
||||
},
|
||||
{
|
||||
"Name": "FTimerHandle",
|
||||
"ManagedType": "UnrealSharp.Engine.FTimerHandle"
|
||||
},
|
||||
{
|
||||
"Name": "FInputActionValue",
|
||||
"ManagedType": "UnrealSharp.EnhancedInput.FInputActionValue"
|
||||
},
|
||||
{
|
||||
"Name": "FRandomStream",
|
||||
"ManagedType": "UnrealSharp.CoreUObject.FRandomStream"
|
||||
},
|
||||
{
|
||||
"Name": "FSubsystemCollectionBaseRef",
|
||||
"ManagedType": "UnrealSharp.UnrealSharpCore.FSubsystemCollectionBaseRef"
|
||||
}
|
||||
],
|
||||
"NativelyTranslatableTypes": [
|
||||
{
|
||||
"Name": "FMoverDataCollection",
|
||||
"HasDestructor": false
|
||||
},
|
||||
{
|
||||
"Name": "FPaintContext",
|
||||
"HasDestructor": false
|
||||
},
|
||||
{
|
||||
"Name": "FGeometry",
|
||||
"HasDestructor": false
|
||||
},
|
||||
{
|
||||
"Name": "FGameplayEffectSpec",
|
||||
"HasDestructor": true
|
||||
},
|
||||
{
|
||||
"Name": "FGameplayEffectSpecHandle",
|
||||
"HasDestructor": true
|
||||
},
|
||||
{
|
||||
"Name": "FKeyEvent",
|
||||
"HasDestructor": false
|
||||
},
|
||||
{
|
||||
"Name": "FInputEvent",
|
||||
"HasDestructor": false
|
||||
},
|
||||
{
|
||||
"Name": "FKey",
|
||||
"HasDestructor": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
5
Plugins/UnrealSharp/Config/DefaultUnrealSharp.ini
Normal file
5
Plugins/UnrealSharp/Config/DefaultUnrealSharp.ini
Normal file
@ -0,0 +1,5 @@
|
||||
[CoreRedirects]
|
||||
+ClassRedirects=(OldName="/Script/CSharpForUE.CSClass",NewName="/Script/UnrealSharpCore.CSClass")
|
||||
+StructRedirects=(OldName="/Script/CSharpForUE.CSScriptStruct",NewName="/Script/UnrealSharpCore.CSScriptStruct")
|
||||
+EnumRedirects=(OldName="/Script/CSharpForUE.CSEnum",NewName="/Script/UnrealSharpCore.CSEnum")
|
||||
+ClassRedirects=(OldName="/Script/UnrealSharpCore.CSDeveloperSettings",NewName="/Script/UnrealSharpCore.CSUnrealSharpSettings")
|
||||
18
Plugins/UnrealSharp/Directory.Packages.props
Normal file
18
Plugins/UnrealSharp/Directory.Packages.props
Normal file
@ -0,0 +1,18 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="LanguageExt.Core" Version="4.4.9" />
|
||||
<PackageVersion Include="Mono.Cecil" Version="0.11.6" />
|
||||
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.13.9" />
|
||||
<PackageVersion Include="Microsoft.Build" Version="17.13.9" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.13.0" />
|
||||
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageVersion Include="Microsoft.Build.Locator" Version="1.9.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
21
Plugins/UnrealSharp/LICENSE
Normal file
21
Plugins/UnrealSharp/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 UnrealSharp
|
||||
|
||||
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.
|
||||
@ -0,0 +1,47 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#ifndef __CORECLR_DELEGATES_H__
|
||||
#define __CORECLR_DELEGATES_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define CORECLR_DELEGATE_CALLTYPE __stdcall
|
||||
#ifdef _WCHAR_T_DEFINED
|
||||
typedef wchar_t char_t;
|
||||
#else
|
||||
typedef unsigned short char_t;
|
||||
#endif
|
||||
#else
|
||||
#define CORECLR_DELEGATE_CALLTYPE
|
||||
typedef char char_t;
|
||||
#endif
|
||||
|
||||
#define UNMANAGEDCALLERSONLY_METHOD ((const char_t*)-1)
|
||||
|
||||
// Signature of delegate returned by coreclr_delegate_type::load_assembly_and_get_function_pointer
|
||||
typedef int (CORECLR_DELEGATE_CALLTYPE *load_assembly_and_get_function_pointer_fn)(
|
||||
const char_t *assembly_path /* Fully qualified path to assembly */,
|
||||
const char_t *type_name /* Assembly qualified type name */,
|
||||
const char_t *method_name /* Public static method name compatible with delegateType */,
|
||||
const char_t *delegate_type_name /* Assembly qualified delegate type name or null
|
||||
or UNMANAGEDCALLERSONLY_METHOD if the method is marked with
|
||||
the UnmanagedCallersOnlyAttribute. */,
|
||||
void *reserved /* Extensibility parameter (currently unused and must be 0) */,
|
||||
/*out*/ void **delegate /* Pointer where to store the function pointer result */);
|
||||
|
||||
// Signature of delegate returned by load_assembly_and_get_function_pointer_fn when delegate_type_name == null (default)
|
||||
typedef int (CORECLR_DELEGATE_CALLTYPE *component_entry_point_fn)(void *arg, int32_t arg_size_in_bytes);
|
||||
|
||||
typedef int (CORECLR_DELEGATE_CALLTYPE *get_function_pointer_fn)(
|
||||
const char_t *type_name /* Assembly qualified type name */,
|
||||
const char_t *method_name /* Public static method name compatible with delegateType */,
|
||||
const char_t *delegate_type_name /* Assembly qualified delegate type name or null,
|
||||
or UNMANAGEDCALLERSONLY_METHOD if the method is marked with
|
||||
the UnmanagedCallersOnlyAttribute. */,
|
||||
void *load_context /* Extensibility parameter (currently unused and must be 0) */,
|
||||
void *reserved /* Extensibility parameter (currently unused and must be 0) */,
|
||||
/*out*/ void **delegate /* Pointer where to store the function pointer result */);
|
||||
|
||||
#endif // __CORECLR_DELEGATES_H__
|
||||
323
Plugins/UnrealSharp/Managed/DotNetRuntime/inc/hostfxr.h
Normal file
323
Plugins/UnrealSharp/Managed/DotNetRuntime/inc/hostfxr.h
Normal file
@ -0,0 +1,323 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#ifndef __HOSTFXR_H__
|
||||
#define __HOSTFXR_H__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define HOSTFXR_CALLTYPE __cdecl
|
||||
#ifdef _WCHAR_T_DEFINED
|
||||
typedef wchar_t char_t;
|
||||
#else
|
||||
typedef unsigned short char_t;
|
||||
#endif
|
||||
#else
|
||||
#define HOSTFXR_CALLTYPE
|
||||
typedef char char_t;
|
||||
#endif
|
||||
|
||||
enum hostfxr_delegate_type
|
||||
{
|
||||
hdt_com_activation,
|
||||
hdt_load_in_memory_assembly,
|
||||
hdt_winrt_activation,
|
||||
hdt_com_register,
|
||||
hdt_com_unregister,
|
||||
hdt_load_assembly_and_get_function_pointer,
|
||||
hdt_get_function_pointer,
|
||||
};
|
||||
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_fn)(const int argc, const char_t **argv);
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_main_startupinfo_fn)(
|
||||
const int argc,
|
||||
const char_t **argv,
|
||||
const char_t *host_path,
|
||||
const char_t *dotnet_root,
|
||||
const char_t *app_path);
|
||||
typedef int32_t(HOSTFXR_CALLTYPE* hostfxr_main_bundle_startupinfo_fn)(
|
||||
const int argc,
|
||||
const char_t** argv,
|
||||
const char_t* host_path,
|
||||
const char_t* dotnet_root,
|
||||
const char_t* app_path,
|
||||
int64_t bundle_header_offset);
|
||||
|
||||
typedef void(HOSTFXR_CALLTYPE *hostfxr_error_writer_fn)(const char_t *message);
|
||||
|
||||
//
|
||||
// Sets a callback which is to be used to write errors to.
|
||||
//
|
||||
// Parameters:
|
||||
// error_writer
|
||||
// A callback function which will be invoked every time an error is to be reported.
|
||||
// Or nullptr to unregister previously registered callback and return to the default behavior.
|
||||
// Return value:
|
||||
// The previously registered callback (which is now unregistered), or nullptr if no previous callback
|
||||
// was registered
|
||||
//
|
||||
// The error writer is registered per-thread, so the registration is thread-local. On each thread
|
||||
// only one callback can be registered. Subsequent registrations overwrite the previous ones.
|
||||
//
|
||||
// By default no callback is registered in which case the errors are written to stderr.
|
||||
//
|
||||
// Each call to the error writer is sort of like writing a single line (the EOL character is omitted).
|
||||
// Multiple calls to the error writer may occure for one failure.
|
||||
//
|
||||
// If the hostfxr invokes functions in hostpolicy as part of its operation, the error writer
|
||||
// will be propagated to hostpolicy for the duration of the call. This means that errors from
|
||||
// both hostfxr and hostpolicy will be reporter through the same error writer.
|
||||
//
|
||||
typedef hostfxr_error_writer_fn(HOSTFXR_CALLTYPE *hostfxr_set_error_writer_fn)(hostfxr_error_writer_fn error_writer);
|
||||
|
||||
typedef void* hostfxr_handle;
|
||||
struct hostfxr_initialize_parameters
|
||||
{
|
||||
size_t size;
|
||||
const char_t *host_path;
|
||||
const char_t *dotnet_root;
|
||||
};
|
||||
|
||||
//
|
||||
// Initializes the hosting components for a dotnet command line running an application
|
||||
//
|
||||
// Parameters:
|
||||
// argc
|
||||
// Number of argv arguments
|
||||
// argv
|
||||
// Command-line arguments for running an application (as if through the dotnet executable).
|
||||
// Only command-line arguments which are accepted by runtime installation are supported, SDK/CLI commands are not supported.
|
||||
// For example 'app.dll app_argument_1 app_argument_2`.
|
||||
// parameters
|
||||
// Optional. Additional parameters for initialization
|
||||
// host_context_handle
|
||||
// On success, this will be populated with an opaque value representing the initialized host context
|
||||
//
|
||||
// Return value:
|
||||
// Success - Hosting components were successfully initialized
|
||||
// HostInvalidState - Hosting components are already initialized
|
||||
//
|
||||
// This function parses the specified command-line arguments to determine the application to run. It will
|
||||
// then find the corresponding .runtimeconfig.json and .deps.json with which to resolve frameworks and
|
||||
// dependencies and prepare everything needed to load the runtime.
|
||||
//
|
||||
// This function only supports arguments for running an application. It does not support SDK commands.
|
||||
//
|
||||
// This function does not load the runtime.
|
||||
//
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_dotnet_command_line_fn)(
|
||||
int argc,
|
||||
const char_t **argv,
|
||||
const struct hostfxr_initialize_parameters *parameters,
|
||||
/*out*/ hostfxr_handle *host_context_handle);
|
||||
|
||||
//
|
||||
// Initializes the hosting components using a .runtimeconfig.json file
|
||||
//
|
||||
// Parameters:
|
||||
// runtime_config_path
|
||||
// Path to the .runtimeconfig.json file
|
||||
// parameters
|
||||
// Optional. Additional parameters for initialization
|
||||
// host_context_handle
|
||||
// On success, this will be populated with an opaque value representing the initialized host context
|
||||
//
|
||||
// Return value:
|
||||
// Success - Hosting components were successfully initialized
|
||||
// Success_HostAlreadyInitialized - Config is compatible with already initialized hosting components
|
||||
// Success_DifferentRuntimeProperties - Config has runtime properties that differ from already initialized hosting components
|
||||
// CoreHostIncompatibleConfig - Config is incompatible with already initialized hosting components
|
||||
//
|
||||
// This function will process the .runtimeconfig.json to resolve frameworks and prepare everything needed
|
||||
// to load the runtime. It will only process the .deps.json from frameworks (not any app/component that
|
||||
// may be next to the .runtimeconfig.json).
|
||||
//
|
||||
// This function does not load the runtime.
|
||||
//
|
||||
// If called when the runtime has already been loaded, this function will check if the specified runtime
|
||||
// config is compatible with the existing runtime.
|
||||
//
|
||||
// Both Success_HostAlreadyInitialized and Success_DifferentRuntimeProperties codes are considered successful
|
||||
// initializations. In the case of Success_DifferentRuntimeProperties, it is left to the consumer to verify that
|
||||
// the difference in properties is acceptable.
|
||||
//
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_initialize_for_runtime_config_fn)(
|
||||
const char_t *runtime_config_path,
|
||||
const struct hostfxr_initialize_parameters *parameters,
|
||||
/*out*/ hostfxr_handle *host_context_handle);
|
||||
|
||||
//
|
||||
// Gets the runtime property value for an initialized host context
|
||||
//
|
||||
// Parameters:
|
||||
// host_context_handle
|
||||
// Handle to the initialized host context
|
||||
// name
|
||||
// Runtime property name
|
||||
// value
|
||||
// Out parameter. Pointer to a buffer with the property value.
|
||||
//
|
||||
// Return value:
|
||||
// The error code result.
|
||||
//
|
||||
// The buffer pointed to by value is owned by the host context. The lifetime of the buffer is only
|
||||
// guaranteed until any of the below occur:
|
||||
// - a 'run' method is called for the host context
|
||||
// - properties are changed via hostfxr_set_runtime_property_value
|
||||
// - the host context is closed via 'hostfxr_close'
|
||||
//
|
||||
// If host_context_handle is nullptr and an active host context exists, this function will get the
|
||||
// property value for the active host context.
|
||||
//
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_property_value_fn)(
|
||||
const hostfxr_handle host_context_handle,
|
||||
const char_t *name,
|
||||
/*out*/ const char_t **value);
|
||||
|
||||
//
|
||||
// Sets the value of a runtime property for an initialized host context
|
||||
//
|
||||
// Parameters:
|
||||
// host_context_handle
|
||||
// Handle to the initialized host context
|
||||
// name
|
||||
// Runtime property name
|
||||
// value
|
||||
// Value to set
|
||||
//
|
||||
// Return value:
|
||||
// The error code result.
|
||||
//
|
||||
// Setting properties is only supported for the first host context, before the runtime has been loaded.
|
||||
//
|
||||
// If the property already exists in the host context, it will be overwritten. If value is nullptr, the
|
||||
// property will be removed.
|
||||
//
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_set_runtime_property_value_fn)(
|
||||
const hostfxr_handle host_context_handle,
|
||||
const char_t *name,
|
||||
const char_t *value);
|
||||
|
||||
//
|
||||
// Gets all the runtime properties for an initialized host context
|
||||
//
|
||||
// Parameters:
|
||||
// host_context_handle
|
||||
// Handle to the initialized host context
|
||||
// count
|
||||
// [in] Size of the keys and values buffers
|
||||
// [out] Number of properties returned (size of keys/values buffers used). If the input value is too
|
||||
// small or keys/values is nullptr, this is populated with the number of available properties
|
||||
// keys
|
||||
// Array of pointers to buffers with runtime property keys
|
||||
// values
|
||||
// Array of pointers to buffers with runtime property values
|
||||
//
|
||||
// Return value:
|
||||
// The error code result.
|
||||
//
|
||||
// The buffers pointed to by keys and values are owned by the host context. The lifetime of the buffers is only
|
||||
// guaranteed until any of the below occur:
|
||||
// - a 'run' method is called for the host context
|
||||
// - properties are changed via hostfxr_set_runtime_property_value
|
||||
// - the host context is closed via 'hostfxr_close'
|
||||
//
|
||||
// If host_context_handle is nullptr and an active host context exists, this function will get the
|
||||
// properties for the active host context.
|
||||
//
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_properties_fn)(
|
||||
const hostfxr_handle host_context_handle,
|
||||
/*inout*/ size_t * count,
|
||||
/*out*/ const char_t **keys,
|
||||
/*out*/ const char_t **values);
|
||||
|
||||
//
|
||||
// Load CoreCLR and run the application for an initialized host context
|
||||
//
|
||||
// Parameters:
|
||||
// host_context_handle
|
||||
// Handle to the initialized host context
|
||||
//
|
||||
// Return value:
|
||||
// If the app was successfully run, the exit code of the application. Otherwise, the error code result.
|
||||
//
|
||||
// The host_context_handle must have been initialized using hostfxr_initialize_for_dotnet_command_line.
|
||||
//
|
||||
// This function will not return until the managed application exits.
|
||||
//
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_run_app_fn)(const hostfxr_handle host_context_handle);
|
||||
|
||||
//
|
||||
// Gets a typed delegate from the currently loaded CoreCLR or from a newly created one.
|
||||
//
|
||||
// Parameters:
|
||||
// host_context_handle
|
||||
// Handle to the initialized host context
|
||||
// type
|
||||
// Type of runtime delegate requested
|
||||
// delegate
|
||||
// An out parameter that will be assigned the delegate.
|
||||
//
|
||||
// Return value:
|
||||
// The error code result.
|
||||
//
|
||||
// If the host_context_handle was initialized using hostfxr_initialize_for_runtime_config,
|
||||
// then all delegate types are supported.
|
||||
// If the host_context_handle was initialized using hostfxr_initialize_for_dotnet_command_line,
|
||||
// then only the following delegate types are currently supported:
|
||||
// hdt_load_assembly_and_get_function_pointer
|
||||
// hdt_get_function_pointer
|
||||
//
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_get_runtime_delegate_fn)(
|
||||
const hostfxr_handle host_context_handle,
|
||||
enum hostfxr_delegate_type type,
|
||||
/*out*/ void **delegate);
|
||||
|
||||
//
|
||||
// Closes an initialized host context
|
||||
//
|
||||
// Parameters:
|
||||
// host_context_handle
|
||||
// Handle to the initialized host context
|
||||
//
|
||||
// Return value:
|
||||
// The error code result.
|
||||
//
|
||||
typedef int32_t(HOSTFXR_CALLTYPE *hostfxr_close_fn)(const hostfxr_handle host_context_handle);
|
||||
|
||||
struct hostfxr_dotnet_environment_sdk_info
|
||||
{
|
||||
size_t size;
|
||||
const char_t* version;
|
||||
const char_t* path;
|
||||
};
|
||||
|
||||
typedef void(HOSTFXR_CALLTYPE* hostfxr_get_dotnet_environment_info_result_fn)(
|
||||
const struct hostfxr_dotnet_environment_info* info,
|
||||
void* result_context);
|
||||
|
||||
struct hostfxr_dotnet_environment_framework_info
|
||||
{
|
||||
size_t size;
|
||||
const char_t* name;
|
||||
const char_t* version;
|
||||
const char_t* path;
|
||||
};
|
||||
|
||||
struct hostfxr_dotnet_environment_info
|
||||
{
|
||||
size_t size;
|
||||
|
||||
const char_t* hostfxr_version;
|
||||
const char_t* hostfxr_commit_hash;
|
||||
|
||||
size_t sdk_count;
|
||||
const hostfxr_dotnet_environment_sdk_info* sdks;
|
||||
|
||||
size_t framework_count;
|
||||
const hostfxr_dotnet_environment_framework_info* frameworks;
|
||||
};
|
||||
|
||||
#endif //__HOSTFXR_H__
|
||||
99
Plugins/UnrealSharp/Managed/DotNetRuntime/inc/nethost.h
Normal file
99
Plugins/UnrealSharp/Managed/DotNetRuntime/inc/nethost.h
Normal file
@ -0,0 +1,99 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#ifndef __NETHOST_H__
|
||||
#define __NETHOST_H__
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef NETHOST_EXPORT
|
||||
#define NETHOST_API __declspec(dllexport)
|
||||
#else
|
||||
// Consuming the nethost as a static library
|
||||
// Shouldn't export attempt to dllimport.
|
||||
#ifdef NETHOST_USE_AS_STATIC
|
||||
#define NETHOST_API
|
||||
#else
|
||||
#define NETHOST_API __declspec(dllimport)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define NETHOST_CALLTYPE __stdcall
|
||||
#ifdef _WCHAR_T_DEFINED
|
||||
typedef wchar_t char_t;
|
||||
#else
|
||||
typedef unsigned short char_t;
|
||||
#endif
|
||||
#else
|
||||
#ifdef NETHOST_EXPORT
|
||||
#define NETHOST_API __attribute__((__visibility__("default")))
|
||||
#else
|
||||
#define NETHOST_API
|
||||
#endif
|
||||
|
||||
#define NETHOST_CALLTYPE
|
||||
typedef char char_t;
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Parameters for get_hostfxr_path
|
||||
//
|
||||
// Fields:
|
||||
// size
|
||||
// Size of the struct. This is used for versioning.
|
||||
//
|
||||
// assembly_path
|
||||
// Path to the compenent's assembly.
|
||||
// If specified, hostfxr is located as if the assembly_path is the apphost
|
||||
//
|
||||
// dotnet_root
|
||||
// Path to directory containing the dotnet executable.
|
||||
// If specified, hostfxr is located as if an application is started using
|
||||
// 'dotnet app.dll', which means it will be searched for under the dotnet_root
|
||||
// path and the assembly_path is ignored.
|
||||
//
|
||||
struct get_hostfxr_parameters {
|
||||
size_t size;
|
||||
const char_t *assembly_path;
|
||||
const char_t *dotnet_root;
|
||||
};
|
||||
|
||||
//
|
||||
// Get the path to the hostfxr library
|
||||
//
|
||||
// Parameters:
|
||||
// buffer
|
||||
// Buffer that will be populated with the hostfxr path, including a null terminator.
|
||||
//
|
||||
// buffer_size
|
||||
// [in] Size of buffer in char_t units.
|
||||
// [out] Size of buffer used in char_t units. If the input value is too small
|
||||
// or buffer is nullptr, this is populated with the minimum required size
|
||||
// in char_t units for a buffer to hold the hostfxr path
|
||||
//
|
||||
// get_hostfxr_parameters
|
||||
// Optional. Parameters that modify the behaviour for locating the hostfxr library.
|
||||
// If nullptr, hostfxr is located using the enviroment variable or global registration
|
||||
//
|
||||
// Return value:
|
||||
// 0 on success, otherwise failure
|
||||
// 0x80008098 - buffer is too small (HostApiBufferTooSmall)
|
||||
//
|
||||
// Remarks:
|
||||
// The full search for the hostfxr library is done on every call. To minimize the need
|
||||
// to call this function multiple times, pass a large buffer (e.g. PATH_MAX).
|
||||
//
|
||||
NETHOST_API int NETHOST_CALLTYPE get_hostfxr_path(
|
||||
char_t * buffer,
|
||||
size_t * buffer_size,
|
||||
const struct get_hostfxr_parameters *parameters);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // __NETHOST_H__
|
||||
252
Plugins/UnrealSharp/Managed/Shared/DotNetUtilities.cs
Normal file
252
Plugins/UnrealSharp/Managed/Shared/DotNetUtilities.cs
Normal file
@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace UnrealSharp.Shared;
|
||||
|
||||
public static class DotNetUtilities
|
||||
{
|
||||
public const string DOTNET_MAJOR_VERSION = "9.0";
|
||||
public const string DOTNET_MAJOR_VERSION_DISPLAY = "net" + DOTNET_MAJOR_VERSION;
|
||||
|
||||
public static string FindDotNetExecutable()
|
||||
{
|
||||
const string DOTNET_WIN = "dotnet.exe";
|
||||
const string DOTNET_UNIX = "dotnet";
|
||||
|
||||
var dotnetExe = OperatingSystem.IsWindows() ? DOTNET_WIN : DOTNET_UNIX;
|
||||
|
||||
var pathVariable = Environment.GetEnvironmentVariable("PATH");
|
||||
|
||||
if (pathVariable == null)
|
||||
{
|
||||
throw new Exception($"Couldn't find {dotnetExe}!");
|
||||
}
|
||||
|
||||
var paths = pathVariable.Split(Path.PathSeparator);
|
||||
|
||||
foreach (var path in paths)
|
||||
{
|
||||
// This is a hack to avoid using the dotnet.exe from the Unreal Engine installation directory.
|
||||
// Can't use the dotnet.exe from the Unreal Engine installation directory because it's .NET 6.0
|
||||
if (!path.Contains(@"\dotnet\"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var dotnetExePath = Path.Combine(path, dotnetExe);
|
||||
|
||||
if (File.Exists(dotnetExePath))
|
||||
{
|
||||
return dotnetExePath;
|
||||
}
|
||||
}
|
||||
|
||||
if ( OperatingSystem.IsMacOS() ) {
|
||||
if ( File.Exists( "/usr/local/share/dotnet/dotnet" ) ) {
|
||||
return "/usr/local/share/dotnet/dotnet";
|
||||
}
|
||||
if ( File.Exists( "/opt/homebrew/bin/dotnet" ) ) {
|
||||
return "/opt/homebrew/bin/dotnet";
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception($"Couldn't find {dotnetExe} in PATH!");
|
||||
}
|
||||
|
||||
public static string GetLatestDotNetSdkPath()
|
||||
{
|
||||
string dotNetExecutable = FindDotNetExecutable();
|
||||
string dotNetExecutableDirectory = Path.GetDirectoryName(dotNetExecutable)!;
|
||||
string dotNetSdkDirectory = Path.Combine(dotNetExecutableDirectory!, "sdk");
|
||||
|
||||
string[] folderPaths = Directory.GetDirectories(dotNetSdkDirectory);
|
||||
|
||||
string highestVersion = "0.0.0";
|
||||
|
||||
foreach (string folderPath in folderPaths)
|
||||
{
|
||||
string folderName = Path.GetFileName(folderPath);
|
||||
|
||||
if (string.IsNullOrEmpty(folderName) || !char.IsDigit(folderName[0]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (string.Compare(folderName, highestVersion, StringComparison.Ordinal) > 0)
|
||||
{
|
||||
highestVersion = folderName;
|
||||
}
|
||||
}
|
||||
|
||||
if (highestVersion == "0.0.0")
|
||||
{
|
||||
throw new Exception("Failed to find the latest .NET SDK version.");
|
||||
}
|
||||
|
||||
if (!highestVersion.StartsWith(DOTNET_MAJOR_VERSION))
|
||||
{
|
||||
throw new Exception($"Failed to find the latest .NET SDK version. Expected version to start with {DOTNET_MAJOR_VERSION} but found: {highestVersion}");
|
||||
}
|
||||
|
||||
return Path.Combine(dotNetSdkDirectory, highestVersion);
|
||||
}
|
||||
|
||||
public static void BuildSolution(string projectRootDirectory, string managedBinariesPath)
|
||||
{
|
||||
if (!Directory.Exists(projectRootDirectory))
|
||||
{
|
||||
throw new Exception($"Couldn't find project root directory: {projectRootDirectory}");
|
||||
}
|
||||
|
||||
if (!Directory.Exists(managedBinariesPath))
|
||||
{
|
||||
Directory.CreateDirectory(managedBinariesPath);
|
||||
}
|
||||
|
||||
Collection<string> arguments = new Collection<string>
|
||||
{
|
||||
"publish",
|
||||
$"-p:PublishDir=\"{managedBinariesPath}\""
|
||||
};
|
||||
|
||||
InvokeDotNet(arguments, projectRootDirectory);
|
||||
}
|
||||
|
||||
public static bool InvokeDotNet(Collection<string> arguments, string? workingDirectory = null)
|
||||
{
|
||||
string dotnetPath = FindDotNetExecutable();
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = dotnetPath,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true
|
||||
};
|
||||
|
||||
foreach (string argument in arguments)
|
||||
{
|
||||
startInfo.ArgumentList.Add(argument);
|
||||
}
|
||||
|
||||
if (workingDirectory != null)
|
||||
{
|
||||
startInfo.WorkingDirectory = workingDirectory;
|
||||
}
|
||||
|
||||
// Set the MSBuild environment variables to the latest .NET SDK that U# supports.
|
||||
// Otherwise, we'll use the .NET SDK that comes with the Unreal Engine.
|
||||
{
|
||||
string latestDotNetSdkPath = GetLatestDotNetSdkPath();
|
||||
startInfo.Environment["MSBuildExtensionsPath"] = latestDotNetSdkPath;
|
||||
startInfo.Environment["MSBUILD_EXE_PATH"] = $@"{latestDotNetSdkPath}\MSBuild.dll";
|
||||
startInfo.Environment["MSBuildSDKsPath"] = $@"{latestDotNetSdkPath}\Sdks";
|
||||
}
|
||||
|
||||
using Process process = new Process();
|
||||
process.StartInfo = startInfo;
|
||||
|
||||
try
|
||||
{
|
||||
StringBuilder outputBuilder = new StringBuilder();
|
||||
process.OutputDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
outputBuilder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
outputBuilder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
if (!process.Start())
|
||||
{
|
||||
throw new Exception("Failed to start process");
|
||||
}
|
||||
|
||||
process.BeginErrorReadLine();
|
||||
process.BeginOutputReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
string errorMessage = outputBuilder.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
errorMessage = "Process exited with non-zero exit code but no output was captured.";
|
||||
}
|
||||
|
||||
throw new Exception($"Process failed with exit code {process.ExitCode}: {errorMessage}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool InvokeUSharpBuildTool(string action,
|
||||
string managedBinariesPath,
|
||||
string projectName,
|
||||
string pluginDirectory,
|
||||
string projectDirectory,
|
||||
string engineDirectory,
|
||||
IEnumerable<KeyValuePair<string, string>>? additionalArguments = null)
|
||||
{
|
||||
string dotNetExe = FindDotNetExecutable();
|
||||
string unrealSharpBuildToolPath = Path.Combine(managedBinariesPath, "UnrealSharpBuildTool.dll");
|
||||
|
||||
if (!File.Exists(unrealSharpBuildToolPath))
|
||||
{
|
||||
throw new Exception($"Failed to find UnrealSharpBuildTool.dll at: {unrealSharpBuildToolPath}");
|
||||
}
|
||||
|
||||
Collection<string> arguments = new Collection<string>
|
||||
{
|
||||
unrealSharpBuildToolPath,
|
||||
|
||||
"--Action",
|
||||
action,
|
||||
|
||||
"--EngineDirectory",
|
||||
$"{engineDirectory}",
|
||||
|
||||
"--ProjectDirectory",
|
||||
$"{projectDirectory}",
|
||||
|
||||
"--ProjectName",
|
||||
projectName,
|
||||
|
||||
"--PluginDirectory",
|
||||
$"{pluginDirectory}",
|
||||
|
||||
"--DotNetPath",
|
||||
$"{dotNetExe}"
|
||||
};
|
||||
|
||||
if (additionalArguments != null)
|
||||
{
|
||||
arguments.Add("--AdditionalArgs");
|
||||
|
||||
foreach (KeyValuePair<string, string> argument in additionalArguments)
|
||||
{
|
||||
arguments.Add($"{argument.Key}={argument.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
return InvokeDotNet(arguments);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
namespace UnrealSharp.Binds;
|
||||
|
||||
public static class NativeBinds
|
||||
{
|
||||
private unsafe static delegate* unmanaged[Cdecl]<char*, char*, int, IntPtr> _getBoundFunction = null;
|
||||
|
||||
public unsafe static void InitializeNativeBinds(IntPtr bindsCallbacks)
|
||||
{
|
||||
if (_getBoundFunction != null)
|
||||
{
|
||||
throw new Exception("NativeBinds.InitializeNativeBinds called twice");
|
||||
}
|
||||
|
||||
_getBoundFunction = (delegate* unmanaged[Cdecl]<char*, char*, int, IntPtr>)bindsCallbacks;
|
||||
}
|
||||
|
||||
public unsafe static IntPtr TryGetBoundFunction(string outerName, string functionName, int functionSize)
|
||||
{
|
||||
if (_getBoundFunction == null)
|
||||
{
|
||||
throw new Exception("NativeBinds not initialized");
|
||||
}
|
||||
|
||||
IntPtr functionPtr = IntPtr.Zero;
|
||||
fixed (char* outerNamePtr = outerName)
|
||||
fixed (char* functionNamePtr = functionName)
|
||||
{
|
||||
functionPtr = _getBoundFunction(outerNamePtr, functionNamePtr, functionSize);
|
||||
}
|
||||
|
||||
if (functionPtr == IntPtr.Zero)
|
||||
{
|
||||
throw new Exception($"Failed to find bound function {functionName} in {outerName}");
|
||||
}
|
||||
|
||||
return functionPtr;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
namespace UnrealSharp.Binds;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
|
||||
public class NativeCallbacksAttribute : Attribute;
|
||||
@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,3 @@
|
||||
namespace UnrealSharp.Core.Attributes;
|
||||
|
||||
public class BindingAttribute : Attribute;
|
||||
@ -0,0 +1,4 @@
|
||||
namespace UnrealSharp.Core.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Struct)]
|
||||
public class BlittableTypeAttribute : Attribute;
|
||||
@ -0,0 +1,17 @@
|
||||
namespace UnrealSharp.Core.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Used to mark a type as generated. Don't use this attribute in your code.
|
||||
/// It's public since glue for user code is generated in the user's project.
|
||||
/// </summary>
|
||||
public class GeneratedTypeAttribute : Attribute
|
||||
{
|
||||
public GeneratedTypeAttribute(string engineName, string fullName = "")
|
||||
{
|
||||
EngineName = engineName;
|
||||
FullName = fullName;
|
||||
}
|
||||
|
||||
public string EngineName;
|
||||
public string FullName;
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace UnrealSharp.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Struct representing a handle to a delegate.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct FDelegateHandle : IEquatable<FDelegateHandle>
|
||||
{
|
||||
public ulong ID;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ID = 0;
|
||||
}
|
||||
|
||||
public static bool operator ==(FDelegateHandle a, FDelegateHandle b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(FDelegateHandle a, FDelegateHandle b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is FDelegateHandle handle && Equals(handle);
|
||||
}
|
||||
|
||||
public bool Equals(FDelegateHandle other)
|
||||
{
|
||||
return ID == other.ID;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ID.GetHashCode();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
using UnrealSharp.Binds;
|
||||
|
||||
namespace UnrealSharp.Core;
|
||||
|
||||
[NativeCallbacks]
|
||||
public static unsafe partial class FCSManagerExporter
|
||||
{
|
||||
public static delegate* unmanaged<IntPtr, IntPtr> FindManagedObject;
|
||||
public static delegate* unmanaged<IntPtr, IntPtr, IntPtr> FindOrCreateManagedInterfaceWrapper;
|
||||
public static delegate* unmanaged<IntPtr> GetCurrentWorldContext;
|
||||
public static delegate* unmanaged<IntPtr> GetCurrentWorldPtr;
|
||||
|
||||
public static UnrealSharpObject WorldContextObject
|
||||
{
|
||||
get
|
||||
{
|
||||
IntPtr worldContextObject = CallGetCurrentWorldContext();
|
||||
IntPtr handle = CallFindManagedObject(worldContextObject);
|
||||
return GCHandleUtilities.GetObjectFromHandlePtr<UnrealSharpObject>(handle)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace UnrealSharp.Core;
|
||||
|
||||
public static class GCHandleUtilities
|
||||
{
|
||||
private static readonly ConcurrentDictionary<AssemblyLoadContext, ConcurrentDictionary<GCHandle, object>> StrongRefsByAssembly = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void OnAlcUnloading(AssemblyLoadContext alc)
|
||||
{
|
||||
StrongRefsByAssembly.TryRemove(alc, out _);
|
||||
}
|
||||
|
||||
public static GCHandle AllocateStrongPointer(object value, object allocator)
|
||||
{
|
||||
return AllocateStrongPointer(value, allocator.GetType().Assembly);
|
||||
}
|
||||
|
||||
public static GCHandle AllocateStrongPointer(object value, Assembly alc)
|
||||
{
|
||||
AssemblyLoadContext? assemblyLoadContext = AssemblyLoadContext.GetLoadContext(alc);
|
||||
|
||||
if (assemblyLoadContext == null)
|
||||
{
|
||||
throw new InvalidOperationException("AssemblyLoadContext is null.");
|
||||
}
|
||||
|
||||
return AllocateStrongPointer(value, assemblyLoadContext);
|
||||
}
|
||||
|
||||
public static GCHandle AllocateStrongPointer(object value, AssemblyLoadContext loadContext)
|
||||
{
|
||||
GCHandle weakHandle = GCHandle.Alloc(value, GCHandleType.Weak);
|
||||
ConcurrentDictionary<GCHandle, object> strongReferences = StrongRefsByAssembly.GetOrAdd(loadContext, alcInstance =>
|
||||
{
|
||||
alcInstance.Unloading += OnAlcUnloading;
|
||||
return new ConcurrentDictionary<GCHandle, object>();
|
||||
});
|
||||
|
||||
strongReferences.TryAdd(weakHandle, value);
|
||||
|
||||
return weakHandle;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GCHandle AllocateWeakPointer(object value) => GCHandle.Alloc(value, GCHandleType.Weak);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GCHandle AllocatePinnedPointer(object value) => GCHandle.Alloc(value, GCHandleType.Pinned);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void Free(GCHandle handle, Assembly? assembly)
|
||||
{
|
||||
if (assembly != null)
|
||||
{
|
||||
AssemblyLoadContext? assemblyLoadContext = AssemblyLoadContext.GetLoadContext(assembly);
|
||||
|
||||
if (assemblyLoadContext == null)
|
||||
{
|
||||
throw new InvalidOperationException("AssemblyLoadContext is null.");
|
||||
}
|
||||
|
||||
if (StrongRefsByAssembly.TryGetValue(assemblyLoadContext, out ConcurrentDictionary<GCHandle, object>? strongReferences))
|
||||
{
|
||||
strongReferences.TryRemove(handle, out _);
|
||||
}
|
||||
}
|
||||
|
||||
handle.Free();
|
||||
}
|
||||
|
||||
public static T? GetObjectFromHandlePtr<T>(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
GCHandle subObjectGcHandle = GCHandle.FromIntPtr(handle);
|
||||
if (!subObjectGcHandle.IsAllocated)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
object? subObject = subObjectGcHandle.Target;
|
||||
if (subObject is T typedObject)
|
||||
{
|
||||
return typedObject;
|
||||
}
|
||||
|
||||
return default;
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
using UnrealSharp.Binds;
|
||||
|
||||
namespace UnrealSharp.Core.Interop;
|
||||
|
||||
[NativeCallbacks]
|
||||
public static unsafe partial class FScriptArrayExporter
|
||||
{
|
||||
public static delegate* unmanaged<UnmanagedArray*, IntPtr> GetData;
|
||||
public static delegate* unmanaged<UnmanagedArray*, int, NativeBool> IsValidIndex;
|
||||
public static delegate* unmanaged<UnmanagedArray*, int> Num;
|
||||
public static delegate* unmanaged<UnmanagedArray*, void> Destroy;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using UnrealSharp.Binds;
|
||||
|
||||
namespace UnrealSharp.Interop;
|
||||
|
||||
[NativeCallbacks]
|
||||
public unsafe partial class FStringExporter
|
||||
{
|
||||
public static delegate* unmanaged<IntPtr, char*, void> MarshalToNativeString;
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
using UnrealSharp.Binds;
|
||||
|
||||
namespace UnrealSharp.Core.Interop;
|
||||
|
||||
[NativeCallbacks]
|
||||
public unsafe partial class ManagedHandleExporter
|
||||
{
|
||||
public static delegate* unmanaged<IntPtr, IntPtr, void> StoreManagedHandle;
|
||||
public static delegate* unmanaged<IntPtr, IntPtr> LoadManagedHandle;
|
||||
public static delegate* unmanaged<IntPtr, IntPtr, int, void> StoreUnmanagedMemory;
|
||||
public static delegate* unmanaged<IntPtr, IntPtr, int, void> LoadUnmanagedMemory;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
using UnrealSharp.Log;
|
||||
|
||||
namespace UnrealSharp.Core;
|
||||
|
||||
[CustomLog]
|
||||
public static partial class LogUnrealSharpCore;
|
||||
@ -0,0 +1,31 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace UnrealSharp.Core;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct ManagedCallbacks
|
||||
{
|
||||
public delegate* unmanaged<IntPtr, IntPtr, char**, IntPtr> ScriptManagerBridge_CreateManagedObject;
|
||||
public delegate* unmanaged<IntPtr, IntPtr, IntPtr> ScriptManagerBridge_CreateNewManagedObjectWrapper;
|
||||
public delegate* unmanaged<IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, int> ScriptManagerBridge_InvokeManagedMethod;
|
||||
public delegate* unmanaged<IntPtr, void> ScriptManagerBridge_InvokeDelegate;
|
||||
public delegate* unmanaged<IntPtr, char*, IntPtr> ScriptManagerBridge_LookupManagedMethod;
|
||||
public delegate* unmanaged<IntPtr, char*, IntPtr> ScriptManagedBridge_LookupManagedType;
|
||||
public delegate* unmanaged<IntPtr, IntPtr, void> ScriptManagedBridge_Dispose;
|
||||
public delegate* unmanaged<IntPtr, void> ScriptManagedBridge_FreeHandle;
|
||||
|
||||
public static void Initialize(IntPtr outManagedCallbacks)
|
||||
{
|
||||
*(ManagedCallbacks*)outManagedCallbacks = new ManagedCallbacks
|
||||
{
|
||||
ScriptManagerBridge_CreateManagedObject = &UnmanagedCallbacks.CreateNewManagedObject,
|
||||
ScriptManagerBridge_CreateNewManagedObjectWrapper = &UnmanagedCallbacks.CreateNewManagedObjectWrapper,
|
||||
ScriptManagerBridge_InvokeManagedMethod = &UnmanagedCallbacks.InvokeManagedMethod,
|
||||
ScriptManagerBridge_InvokeDelegate = &UnmanagedCallbacks.InvokeDelegate,
|
||||
ScriptManagerBridge_LookupManagedMethod = &UnmanagedCallbacks.LookupManagedMethod,
|
||||
ScriptManagedBridge_LookupManagedType = &UnmanagedCallbacks.LookupManagedType,
|
||||
ScriptManagedBridge_Dispose = &UnmanagedCallbacks.Dispose,
|
||||
ScriptManagedBridge_FreeHandle = &UnmanagedCallbacks.FreeHandle,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
namespace UnrealSharp.Core.Marshallers;
|
||||
|
||||
public static class BitfieldBoolMarshaller
|
||||
{
|
||||
private const int BoolSize = sizeof(NativeBool);
|
||||
|
||||
public static void ToNative(IntPtr valuePtr, byte fieldMask, bool value)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var byteValue = (byte*)valuePtr;
|
||||
var mask = value ? fieldMask : byte.MinValue;
|
||||
*byteValue = (byte)((*byteValue & ~fieldMask) | mask);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool FromNative(IntPtr valuePtr, byte fieldMask)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var byteValue = (byte*)valuePtr;
|
||||
return (*byteValue & fieldMask) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UnrealSharp.Core.Marshallers;
|
||||
|
||||
public static class BlittableMarshaller<T> where T : unmanaged, allows ref struct
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToNative(IntPtr nativeBuffer, int arrayIndex, T obj)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
ToNative(nativeBuffer, arrayIndex, obj, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ToNative(IntPtr nativeBuffer, int arrayIndex, T obj, int size)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
*(T*)(nativeBuffer + arrayIndex * size) = obj;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T FromNative(IntPtr nativeBuffer, int arrayIndex)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return FromNative(nativeBuffer, arrayIndex, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T FromNative(IntPtr nativeBuffer, int arrayIndex, int size)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return *(T*)(nativeBuffer + arrayIndex * size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
namespace UnrealSharp.Core.Marshallers;
|
||||
|
||||
public static class BoolMarshaller
|
||||
{
|
||||
public static void ToNative(IntPtr nativeBuffer, int arrayIndex, bool obj)
|
||||
{
|
||||
BlittableMarshaller<NativeBool>.ToNative(nativeBuffer, arrayIndex, obj.ToNativeBool());
|
||||
}
|
||||
|
||||
public static bool FromNative(IntPtr nativeBuffer, int arrayIndex)
|
||||
{
|
||||
return BlittableMarshaller<NativeBool>.FromNative(nativeBuffer, arrayIndex).ToManagedBool();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
namespace UnrealSharp.Core.Marshallers;
|
||||
|
||||
public static class EnumMarshaller<T> where T : Enum
|
||||
{
|
||||
public static T FromNative(IntPtr nativeBuffer, int arrayIndex)
|
||||
{
|
||||
byte value = BlittableMarshaller<byte>.FromNative(nativeBuffer, arrayIndex);
|
||||
return (T) Enum.ToObject(typeof(T), value);
|
||||
}
|
||||
|
||||
public static void ToNative(IntPtr nativeBuffer, int arrayIndex, T obj)
|
||||
{
|
||||
byte value = Convert.ToByte(obj);
|
||||
BlittableMarshaller<byte>.ToNative(nativeBuffer, arrayIndex, value);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace UnrealSharp;
|
||||
|
||||
public interface MarshalledStruct<Self> where Self : MarshalledStruct<Self>, allows ref struct
|
||||
{
|
||||
public static abstract IntPtr GetNativeClassPtr();
|
||||
|
||||
public static abstract int GetNativeDataSize();
|
||||
|
||||
public static abstract Self FromNative(IntPtr buffer);
|
||||
|
||||
public void ToNative(IntPtr buffer);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace UnrealSharp.Core.Marshallers;
|
||||
|
||||
public static class MarshallingDelegates<T>
|
||||
{
|
||||
public delegate void ToNative(IntPtr nativeBuffer, int arrayIndex, T obj);
|
||||
public delegate T FromNative(IntPtr nativeBuffer, int arrayIndex);
|
||||
public delegate void DestructInstance(IntPtr nativeBuffer, int arrayIndex);
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
namespace UnrealSharp.Core.Marshallers;
|
||||
|
||||
public static class ObjectMarshaller<T> where T : UnrealSharpObject
|
||||
{
|
||||
public static void ToNative(IntPtr nativeBuffer, int arrayIndex, T obj)
|
||||
{
|
||||
IntPtr uObjectPosition = nativeBuffer + arrayIndex * IntPtr.Size;
|
||||
|
||||
unsafe
|
||||
{
|
||||
*(IntPtr*)uObjectPosition = obj?.NativeObject ?? IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public static T FromNative(IntPtr nativeBuffer, int arrayIndex)
|
||||
{
|
||||
IntPtr uObjectPointer = BlittableMarshaller<IntPtr>.FromNative(nativeBuffer, arrayIndex);
|
||||
|
||||
if (uObjectPointer == IntPtr.Zero)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
IntPtr handle = FCSManagerExporter.CallFindManagedObject(uObjectPointer);
|
||||
return GCHandleUtilities.GetObjectFromHandlePtr<T>(handle)!;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
using UnrealSharp.Interop;
|
||||
|
||||
namespace UnrealSharp.Core.Marshallers;
|
||||
|
||||
public static class StringMarshaller
|
||||
{
|
||||
public static void ToNative(IntPtr nativeBuffer, int arrayIndex, string obj)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
if (string.IsNullOrEmpty(obj))
|
||||
{
|
||||
//Guard against C# null strings (use string.Empty instead)
|
||||
obj = string.Empty;
|
||||
}
|
||||
|
||||
IntPtr unrealString = nativeBuffer + arrayIndex * sizeof(UnmanagedArray);
|
||||
|
||||
fixed (char* stringPtr = obj)
|
||||
{
|
||||
FStringExporter.CallMarshalToNativeString(unrealString, stringPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string FromNative(IntPtr nativeBuffer, int arrayIndex)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
UnmanagedArray unrealString = BlittableMarshaller<UnmanagedArray>.FromNative(nativeBuffer, arrayIndex);
|
||||
return unrealString.Data == IntPtr.Zero ? string.Empty : new string((char*) unrealString.Data);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DestructInstance(IntPtr nativeBuffer, int arrayIndex)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
UnmanagedArray* unrealString = (UnmanagedArray*) (nativeBuffer + arrayIndex * sizeof(UnmanagedArray));
|
||||
unrealString->Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
namespace UnrealSharp.Core.Marshallers;
|
||||
|
||||
public static class StructMarshaller<T> where T : MarshalledStruct<T>
|
||||
{
|
||||
public static T FromNative(IntPtr nativeBuffer, int arrayIndex)
|
||||
{
|
||||
return T.FromNative(nativeBuffer + arrayIndex * T.GetNativeDataSize());
|
||||
}
|
||||
|
||||
public static void ToNative(IntPtr nativeBuffer, int arrayIndex, T obj)
|
||||
{
|
||||
obj.ToNative(nativeBuffer + arrayIndex * T.GetNativeDataSize());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace UnrealSharp.Engine.Core.Modules;
|
||||
|
||||
public interface IModuleInterface
|
||||
{
|
||||
public void StartupModule();
|
||||
public void ShutdownModule();
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
namespace UnrealSharp.Core;
|
||||
|
||||
// Bools are not blittable, so we need to convert them to bytes
|
||||
public enum NativeBool : byte
|
||||
{
|
||||
False = 0,
|
||||
True = 1
|
||||
}
|
||||
|
||||
public static class BoolConverter
|
||||
{
|
||||
public static NativeBool ToNativeBool(this bool value)
|
||||
{
|
||||
return value ? NativeBool.True : NativeBool.False;
|
||||
}
|
||||
|
||||
public static bool ToManagedBool(this NativeBool value)
|
||||
{
|
||||
byte byteValue = (byte) value;
|
||||
return byteValue != 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using UnrealSharp.Core.Interop;
|
||||
using UnrealSharp.Core.Marshallers;
|
||||
|
||||
namespace UnrealSharp.Core;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct UnmanagedArray
|
||||
{
|
||||
public IntPtr Data;
|
||||
public int ArrayNum;
|
||||
public int ArrayMax;
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (UnmanagedArray* ptr = &this)
|
||||
{
|
||||
FScriptArrayExporter.CallDestroy(ptr);
|
||||
}
|
||||
|
||||
Data = IntPtr.Zero;
|
||||
ArrayNum = 0;
|
||||
ArrayMax = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> ToBlittableList<T>() where T : unmanaged
|
||||
{
|
||||
List<T> list = new List<T>(ArrayNum);
|
||||
|
||||
unsafe
|
||||
{
|
||||
T* data = (T*) Data.ToPointer();
|
||||
for (int i = 0; i < ArrayNum; i++)
|
||||
{
|
||||
list.Add(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<T> ToListWithMarshaller<T>(Func<IntPtr, int, T> resolver)
|
||||
{
|
||||
List<T> list = new List<T>(ArrayNum);
|
||||
|
||||
for (int i = 0; i < ArrayNum; i++)
|
||||
{
|
||||
list.Add(resolver(Data, i));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void ForEachWithMarshaller<T>(Func<IntPtr, int, T> resolver, Action<T> action)
|
||||
{
|
||||
for (int i = 0; i < ArrayNum; i++)
|
||||
{
|
||||
T item = resolver(Data, i);
|
||||
action(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,254 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnrealSharp.Core.Attributes;
|
||||
using UnrealSharp.Core.Marshallers;
|
||||
|
||||
namespace UnrealSharp.Core;
|
||||
|
||||
public static class UnmanagedCallbacks
|
||||
{
|
||||
[UnmanagedCallersOnly]
|
||||
public static unsafe IntPtr CreateNewManagedObject(IntPtr nativeObject, IntPtr typeHandlePtr, char** error)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (nativeObject == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(nativeObject));
|
||||
}
|
||||
|
||||
Type? type = GCHandleUtilities.GetObjectFromHandlePtr<Type>(typeHandlePtr);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new InvalidOperationException("The provided type handle does not point to a valid type.");
|
||||
}
|
||||
|
||||
return UnrealSharpObject.Create(type, nativeObject);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogUnrealSharpCore.LogError($"Failed to create new managed object: {ex.Message}");
|
||||
*error = (char*)Marshal.StringToHGlobalUni(ex.ToString());
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static IntPtr CreateNewManagedObjectWrapper(IntPtr managedObjectHandle, IntPtr typeHandlePtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (managedObjectHandle == IntPtr.Zero)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(managedObjectHandle));
|
||||
}
|
||||
|
||||
Type? type = GCHandleUtilities.GetObjectFromHandlePtr<Type>(typeHandlePtr);
|
||||
|
||||
if (type is null)
|
||||
{
|
||||
throw new InvalidOperationException("The provided type handle does not point to a valid type.");
|
||||
}
|
||||
|
||||
object? managedObject = GCHandleUtilities.GetObjectFromHandlePtr<object>(managedObjectHandle);
|
||||
if (managedObject is null)
|
||||
{
|
||||
throw new InvalidOperationException("The provided managed object handle does not point to a valid object.");
|
||||
}
|
||||
|
||||
MethodInfo? wrapMethod = type.GetMethod("Wrap", BindingFlags.Public | BindingFlags.Static);
|
||||
if (wrapMethod is null)
|
||||
{
|
||||
throw new InvalidOperationException("The provided type does not have a static Wrap method.");
|
||||
}
|
||||
|
||||
object? createdObject = wrapMethod.Invoke(null, [managedObject]);
|
||||
if (createdObject is null)
|
||||
{
|
||||
throw new InvalidOperationException("The Wrap method did not return a valid object.");
|
||||
}
|
||||
|
||||
return GCHandle.ToIntPtr(GCHandleUtilities.AllocateStrongPointer(createdObject, createdObject.GetType().Assembly));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogUnrealSharpCore.LogError($"Failed to create new managed object: {ex.Message}");
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static unsafe IntPtr LookupManagedMethod(IntPtr typeHandlePtr, char* methodName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Type? type = GCHandleUtilities.GetObjectFromHandlePtr<Type>(typeHandlePtr);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new Exception("Invalid type handle");
|
||||
}
|
||||
|
||||
string methodNameString = new string(methodName);
|
||||
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
|
||||
Type? currentType = type;
|
||||
|
||||
while (currentType != null)
|
||||
{
|
||||
MethodInfo? method = currentType.GetMethod(methodNameString, flags);
|
||||
|
||||
if (method != null)
|
||||
{
|
||||
IntPtr functionPtr = method.MethodHandle.GetFunctionPointer();
|
||||
GCHandle methodHandle = GCHandleUtilities.AllocateStrongPointer(functionPtr, type.Assembly);
|
||||
return GCHandle.ToIntPtr(methodHandle);
|
||||
}
|
||||
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogUnrealSharpCore.LogError($"Exception while trying to look up managed method: {e.Message}");
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static unsafe IntPtr LookupManagedType(IntPtr assemblyHandle, char* fullTypeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string fullTypeNameString = new string(fullTypeName);
|
||||
Assembly? loadedAssembly = GCHandleUtilities.GetObjectFromHandlePtr<Assembly>(assemblyHandle);
|
||||
|
||||
if (loadedAssembly == null)
|
||||
{
|
||||
throw new InvalidOperationException("The provided assembly handle does not point to a valid assembly.");
|
||||
}
|
||||
|
||||
return FindTypeInAssembly(loadedAssembly, fullTypeNameString);
|
||||
}
|
||||
catch (TypeLoadException ex)
|
||||
{
|
||||
LogUnrealSharpCore.LogError($"TypeLoadException while trying to look up managed type: {ex.Message}");
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private static IntPtr FindTypeInAssembly(Assembly assembly, string fullTypeName)
|
||||
{
|
||||
Type[] types = assembly.GetTypes();
|
||||
foreach (Type type in types)
|
||||
{
|
||||
foreach (CustomAttributeData attributeData in type.CustomAttributes)
|
||||
{
|
||||
if (attributeData.AttributeType.FullName != typeof(GeneratedTypeAttribute).FullName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (attributeData.ConstructorArguments.Count != 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string fullName = (string)attributeData.ConstructorArguments[1].Value!;
|
||||
if (fullName == fullTypeName)
|
||||
{
|
||||
return GCHandle.ToIntPtr(GCHandleUtilities.AllocateStrongPointer(type, assembly));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static unsafe int InvokeManagedMethod(IntPtr managedObjectHandle,
|
||||
IntPtr methodHandlePtr,
|
||||
IntPtr argumentsBuffer,
|
||||
IntPtr returnValueBuffer,
|
||||
IntPtr exceptionTextBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
IntPtr? methodHandle = GCHandleUtilities.GetObjectFromHandlePtr<IntPtr>(methodHandlePtr);
|
||||
object? managedObject = GCHandleUtilities.GetObjectFromHandlePtr<object>(managedObjectHandle);
|
||||
|
||||
if (methodHandle == null || managedObject == null)
|
||||
{
|
||||
throw new Exception("Invalid method or target handle");
|
||||
}
|
||||
|
||||
delegate*<object, IntPtr, IntPtr, void> methodPtr = (delegate*<object, IntPtr, IntPtr, void>) methodHandle;
|
||||
methodPtr(managedObject, argumentsBuffer, returnValueBuffer);
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StringMarshaller.ToNative(exceptionTextBuffer, 0, ex.ToString());
|
||||
LogUnrealSharpCore.LogError($"Exception during InvokeManagedMethod: {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void InvokeDelegate(IntPtr delegatePtr)
|
||||
{
|
||||
try
|
||||
{
|
||||
Delegate? foundDelegate = GCHandleUtilities.GetObjectFromHandlePtr<Delegate>(delegatePtr);
|
||||
|
||||
if (foundDelegate == null)
|
||||
{
|
||||
throw new Exception("Invalid delegate handle");
|
||||
}
|
||||
|
||||
foundDelegate.DynamicInvoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogUnrealSharpCore.LogError($"Exception during InvokeDelegate: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void Dispose(IntPtr handle, IntPtr assemblyHandle)
|
||||
{
|
||||
GCHandle foundHandle = GCHandle.FromIntPtr(handle);
|
||||
|
||||
if (!foundHandle.IsAllocated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundHandle.Target is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
Assembly? foundAssembly = GCHandleUtilities.GetObjectFromHandlePtr<Assembly>(assemblyHandle);
|
||||
GCHandleUtilities.Free(foundHandle, foundAssembly);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void FreeHandle(IntPtr handle)
|
||||
{
|
||||
GCHandle foundHandle = GCHandle.FromIntPtr(handle);
|
||||
if (!foundHandle.IsAllocated) return;
|
||||
|
||||
if (foundHandle.Target is IDisposable disposable)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
foundHandle.Free();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UnrealSharp.Binds\UnrealSharp.Binds.csproj" />
|
||||
<ProjectReference Include="..\UnrealSharp.Log\UnrealSharp.Log.csproj" />
|
||||
|
||||
<ProjectReference Include="..\UnrealSharp.SourceGenerators\UnrealSharp.SourceGenerators.csproj">
|
||||
<OutputItemType>Analyzer</OutputItemType>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,44 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace UnrealSharp.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a UObject in Unreal Engine. Don't inherit from this class directly, use a CoreUObject.Object instead.
|
||||
/// </summary>
|
||||
public class UnrealSharpObject : IDisposable
|
||||
{
|
||||
internal static unsafe IntPtr Create(Type typeToCreate, IntPtr nativeObjectPtr)
|
||||
{
|
||||
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
ConstructorInfo? foundDefaultCtor = typeToCreate.GetConstructor(bindingFlags, Type.EmptyTypes);
|
||||
|
||||
if (foundDefaultCtor == null)
|
||||
{
|
||||
LogUnrealSharpCore.LogError("Failed to find default constructor for type: " + typeToCreate.FullName);
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
delegate*<object, void> foundConstructor = (delegate*<object, void>) foundDefaultCtor.MethodHandle.GetFunctionPointer();
|
||||
|
||||
UnrealSharpObject createdObject = (UnrealSharpObject) RuntimeHelpers.GetUninitializedObject(typeToCreate);
|
||||
createdObject.NativeObject = nativeObjectPtr;
|
||||
|
||||
foundConstructor(createdObject);
|
||||
|
||||
return GCHandle.ToIntPtr(GCHandleUtilities.AllocateStrongPointer(createdObject, typeToCreate.Assembly));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The pointer to the UObject that this C# object represents.
|
||||
/// </summary>
|
||||
public IntPtr NativeObject { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Dispose()
|
||||
{
|
||||
NativeObject = IntPtr.Zero;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
using UnrealSharp.Binds;
|
||||
using UnrealSharp.Core;
|
||||
|
||||
namespace UnrealSharp.Editor.Interop;
|
||||
|
||||
[NativeCallbacks]
|
||||
public static unsafe partial class FUnrealSharpEditorModuleExporter
|
||||
{
|
||||
public static delegate* unmanaged<FManagedUnrealSharpEditorCallbacks, void> InitializeUnrealSharpEditorCallbacks;
|
||||
public static delegate* unmanaged<out UnmanagedArray, void> GetProjectPaths;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
using UnrealSharp.Log;
|
||||
|
||||
namespace UnrealSharp.Editor;
|
||||
|
||||
[CustomLog]
|
||||
public static partial class LogUnrealSharpEditor;
|
||||
@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssembliesName>true</CopyLocalLockFileAssembliesName>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DefineConstants>WITH_EDITOR</DefineConstants>
|
||||
<DefineConstants Condition="'$(DisableWithEditor)' == 'true'">$(DefineConstants.Replace('WITH_EDITOR;', '').Replace('WITH_EDITOR', ''))</DefineConstants>
|
||||
<DefineConstants Condition="'$(DefineAdditionalConstants)' != ''">$(DefineConstants);$(DefineAdditionalConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UnrealSharp.Plugins\UnrealSharp.Plugins.csproj" />
|
||||
<ProjectReference Include="..\UnrealSharp.SourceGenerators\UnrealSharp.SourceGenerators.csproj">
|
||||
<OutputItemType>Analyzer</OutputItemType>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\UnrealSharp\UnrealSharp.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="UnrealSharpWeaver">
|
||||
<HintPath>..\..\..\Binaries\Managed\UnrealSharpWeaver.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Build" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Microsoft.Build.Utilities.Core" ExcludeAssets="runtime" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,230 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Microsoft.Build.Evaluation;
|
||||
using Microsoft.Build.Execution;
|
||||
using Microsoft.Build.Framework;
|
||||
using UnrealSharp.Core;
|
||||
using UnrealSharp.Core.Marshallers;
|
||||
using UnrealSharp.Editor.Interop;
|
||||
using UnrealSharp.Engine.Core.Modules;
|
||||
using UnrealSharp.Shared;
|
||||
using UnrealSharpWeaver;
|
||||
|
||||
namespace UnrealSharp.Editor;
|
||||
|
||||
// TODO: Automate managed callbacks so we easily can make calls from native to managed.
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct FManagedUnrealSharpEditorCallbacks
|
||||
{
|
||||
public delegate* unmanaged<char*, char*, char*, LoggerVerbosity, IntPtr, NativeBool, NativeBool> BuildProjects;
|
||||
public delegate* unmanaged<void> ForceManagedGC;
|
||||
public delegate* unmanaged<char*, IntPtr, NativeBool> OpenSolution;
|
||||
public delegate* unmanaged<char*, void> AddProjectToCollection;
|
||||
|
||||
public FManagedUnrealSharpEditorCallbacks()
|
||||
{
|
||||
BuildProjects = &ManagedUnrealSharpEditorCallbacks.Build;
|
||||
ForceManagedGC = &ManagedUnrealSharpEditorCallbacks.ForceManagedGC;
|
||||
OpenSolution = &ManagedUnrealSharpEditorCallbacks.OpenSolution;
|
||||
AddProjectToCollection = &ManagedUnrealSharpEditorCallbacks.AddProjectToCollection;
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorCollectingLogger : ILogger
|
||||
{
|
||||
public StringBuilder ErrorLog { get; } = new();
|
||||
public LoggerVerbosity Verbosity { get; set; }
|
||||
public string Parameters { get; set; } = string.Empty;
|
||||
|
||||
public ErrorCollectingLogger(LoggerVerbosity verbosity = LoggerVerbosity.Normal)
|
||||
{
|
||||
Verbosity = verbosity;
|
||||
}
|
||||
|
||||
public void Initialize(IEventSource eventSource)
|
||||
{
|
||||
eventSource.ErrorRaised += (sender, e) =>
|
||||
{
|
||||
string fileName = Path.GetFileName(e.File);
|
||||
|
||||
ErrorLog.AppendLine($"{fileName}({e.LineNumber},{e.ColumnNumber}): {e.Message}");
|
||||
ErrorLog.AppendLine();
|
||||
};
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static class ManagedUnrealSharpEditorCallbacks
|
||||
{
|
||||
private static readonly ProjectCollection ProjectCollection = new();
|
||||
private static readonly BuildManager UnrealSharpBuildManager = new("UnrealSharpBuildManager");
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
FUnrealSharpEditorModuleExporter.CallGetProjectPaths(out UnmanagedArray projectPaths);
|
||||
List<string> projectPathsList = projectPaths.ToListWithMarshaller(StringMarshaller.FromNative);
|
||||
|
||||
foreach (string projectPath in projectPathsList)
|
||||
{
|
||||
ProjectCollection.LoadProject(projectPath);
|
||||
}
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static unsafe NativeBool Build(char* solutionPath,
|
||||
char* outputPath,
|
||||
char* buildConfiguration,
|
||||
LoggerVerbosity loggerVerbosity,
|
||||
IntPtr exceptionBuffer,
|
||||
NativeBool buildSolution)
|
||||
{
|
||||
try
|
||||
{
|
||||
string buildConfigurationString = new string(buildConfiguration);
|
||||
|
||||
if (buildSolution == NativeBool.True)
|
||||
{
|
||||
ErrorCollectingLogger logger = new ErrorCollectingLogger(loggerVerbosity);
|
||||
BuildParameters buildParameters = new(ProjectCollection)
|
||||
{
|
||||
Loggers = new List<ILogger> { logger }
|
||||
};
|
||||
|
||||
Dictionary<string, string?> globalProperties = new()
|
||||
{
|
||||
["Configuration"] = buildConfigurationString,
|
||||
};
|
||||
|
||||
BuildRequestData buildRequest = new BuildRequestData(
|
||||
new string(solutionPath),
|
||||
globalProperties,
|
||||
null,
|
||||
new[] { "Build" },
|
||||
null
|
||||
);
|
||||
|
||||
BuildResult result = UnrealSharpBuildManager.Build(buildParameters, buildRequest);
|
||||
if (result.OverallResult == BuildResultCode.Failure)
|
||||
{
|
||||
throw new Exception(logger.ErrorLog.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
Weave(outputPath, buildConfigurationString);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
StringMarshaller.ToNative(exceptionBuffer, 0, exception.Message);
|
||||
return NativeBool.False;
|
||||
}
|
||||
|
||||
return NativeBool.True;
|
||||
}
|
||||
|
||||
static unsafe void Weave(char* outputPath, string buildConfiguration)
|
||||
{
|
||||
List<string> assemblyPaths = new();
|
||||
foreach (Project? projectFile in ProjectCollection.LoadedProjects
|
||||
.Where(p => p.GetPropertyValue("ExcludeFromWeaver") != "true"))
|
||||
{
|
||||
string projectName = Path.GetFileNameWithoutExtension(projectFile.FullPath);
|
||||
string assemblyPath = Path.Combine(projectFile.DirectoryPath, "bin",
|
||||
buildConfiguration, DotNetUtilities.DOTNET_MAJOR_VERSION_DISPLAY, projectName + ".dll");
|
||||
|
||||
assemblyPaths.Add(assemblyPath);
|
||||
}
|
||||
|
||||
WeaverOptions weaverOptions = new WeaverOptions
|
||||
{
|
||||
AssemblyPaths = assemblyPaths,
|
||||
OutputDirectory = new string(outputPath),
|
||||
};
|
||||
|
||||
Program.Weave(weaverOptions);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static void ForceManagedGC()
|
||||
{
|
||||
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
|
||||
GC.WaitForPendingFinalizers();
|
||||
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static unsafe NativeBool OpenSolution(char* solutionPath, IntPtr exceptionBuffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
string solutionFilePath = new (solutionPath);
|
||||
|
||||
if (!File.Exists(solutionFilePath))
|
||||
{
|
||||
throw new FileNotFoundException($"Solution not found at path \"{solutionFilePath}\"");
|
||||
}
|
||||
|
||||
ProcessStartInfo? startInfo = null;
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
startInfo = new ProcessStartInfo("cmd.exe", $"/c start \"\" \"{solutionFilePath}\"");
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
startInfo = new ProcessStartInfo("open", solutionFilePath);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
startInfo = new ProcessStartInfo("xdg-open", solutionFilePath);
|
||||
}
|
||||
|
||||
if (startInfo == null)
|
||||
{
|
||||
throw new PlatformNotSupportedException("Unsupported platform.");
|
||||
}
|
||||
|
||||
startInfo.WorkingDirectory = Path.GetDirectoryName(solutionFilePath);
|
||||
startInfo.Environment["MsBuildExtensionPath"] = null;
|
||||
startInfo.Environment["MSBUILD_EXE_PATH"] = null;
|
||||
startInfo.Environment["MsBuildSDKsPath"] = null;
|
||||
|
||||
Process.Start(startInfo);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
StringMarshaller.ToNative(exceptionBuffer, 0, exception.Message);
|
||||
return NativeBool.False;
|
||||
}
|
||||
|
||||
return NativeBool.True;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
public static unsafe void AddProjectToCollection(char* projectPath)
|
||||
{
|
||||
string projectPathString = new string(projectPath);
|
||||
|
||||
if (ProjectCollection.LoadedProjects.All(p => p.FullPath != projectPathString))
|
||||
{
|
||||
ProjectCollection.LoadProject(projectPathString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FUnrealSharpEditor : IModuleInterface
|
||||
{
|
||||
public void StartupModule()
|
||||
{
|
||||
FManagedUnrealSharpEditorCallbacks callbacks = new FManagedUnrealSharpEditorCallbacks();
|
||||
FUnrealSharpEditorModuleExporter.CallInitializeUnrealSharpEditorCallbacks(callbacks);
|
||||
ManagedUnrealSharpEditorCallbacks.Initialize();
|
||||
}
|
||||
|
||||
public void ShutdownModule()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace UnrealSharp.EditorSourceGenerators;
|
||||
|
||||
struct AssemblyClassInfo(string fullName, string filePath)
|
||||
{
|
||||
public string FullName = fullName;
|
||||
public string FilePath = filePath;
|
||||
}
|
||||
|
||||
[Generator]
|
||||
public class ClassFilePathGenerator : IIncrementalGenerator
|
||||
{
|
||||
private static string GetRelativePath(string filePath)
|
||||
{
|
||||
filePath = filePath.Replace("\\", "/");
|
||||
|
||||
int index = filePath.IndexOf("/Script", StringComparison.OrdinalIgnoreCase);
|
||||
if (index >= 0)
|
||||
{
|
||||
return filePath.Substring(index);
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var syntaxProvider = context.SyntaxProvider.CreateSyntaxProvider(
|
||||
static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax,
|
||||
static (context, _) =>
|
||||
{
|
||||
return context.SemanticModel.GetDeclaredSymbol(context.Node) is INamedTypeSymbol classSymbol
|
||||
? (new[] { new AssemblyClassInfo(classSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), context.Node.SyntaxTree.FilePath) })
|
||||
: Array.Empty<AssemblyClassInfo>();
|
||||
})
|
||||
.SelectMany((results, _) => results)
|
||||
.Where(i => i.FilePath != null)
|
||||
.Collect()
|
||||
.Combine(context.CompilationProvider);
|
||||
|
||||
context.RegisterSourceOutput(syntaxProvider, (outputContext, info) =>
|
||||
{
|
||||
var (classes, compilation) = info;
|
||||
|
||||
Dictionary<string, string> classDictionary = new Dictionary<string, string>();
|
||||
|
||||
foreach (AssemblyClassInfo classInfo in classes)
|
||||
{
|
||||
string className = classInfo.FullName;
|
||||
string? relativeFilePath = GetRelativePath(classInfo.FilePath);
|
||||
|
||||
if (!string.IsNullOrEmpty(className) && !string.IsNullOrEmpty(relativeFilePath))
|
||||
{
|
||||
classDictionary[className] = relativeFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder sourceBuilder = new StringBuilder();
|
||||
|
||||
sourceBuilder.AppendLine("using System.Collections.Generic;");
|
||||
sourceBuilder.AppendLine("using System.Runtime.CompilerServices;");
|
||||
sourceBuilder.AppendLine($"namespace {compilation.AssemblyName};");
|
||||
|
||||
sourceBuilder.AppendLine("public static class ClassFileMap");
|
||||
sourceBuilder.AppendLine("{");
|
||||
|
||||
sourceBuilder.AppendLine(" [ModuleInitializer]");
|
||||
sourceBuilder.AppendLine(" public static void Initialize()");
|
||||
sourceBuilder.AppendLine(" {");
|
||||
|
||||
foreach (KeyValuePair<string, string> kvp in classDictionary)
|
||||
{
|
||||
sourceBuilder.AppendLine($" AddClassFile(\"{kvp.Key}\", \"{kvp.Value}\");");
|
||||
}
|
||||
|
||||
sourceBuilder.AppendLine(" }");
|
||||
|
||||
sourceBuilder.AppendLine(" public unsafe static void AddClassFile(string className, string filePath)");
|
||||
sourceBuilder.AppendLine(" {");
|
||||
sourceBuilder.AppendLine(" fixed (char* ptr1 = className)");
|
||||
sourceBuilder.AppendLine(" fixed (char* ptr2 = filePath)");
|
||||
sourceBuilder.AppendLine(" {");
|
||||
sourceBuilder.AppendLine(" UnrealSharp.Interop.FCSTypeRegistryExporter.CallRegisterClassToFilePath(ptr1, ptr2);");
|
||||
sourceBuilder.AppendLine(" }");
|
||||
sourceBuilder.AppendLine(" }");
|
||||
|
||||
sourceBuilder.AppendLine("}");
|
||||
|
||||
outputContext.AddSource("ClassFileMap.generated.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<NoWarn>$(NoWarn);1570;0649;0169;0108;0109</NoWarn>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,76 @@
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace UnrealSharp.ExtensionSourceGenerators;
|
||||
|
||||
public class ActorComponentExtensionGenerator : ExtensionGenerator
|
||||
{
|
||||
public override void Generate(ref StringBuilder builder, INamedTypeSymbol classSymbol)
|
||||
{
|
||||
GenerateConstructMethod(ref builder, classSymbol);
|
||||
GenerateComponentGetter(ref builder, classSymbol);
|
||||
}
|
||||
|
||||
private void GenerateConstructMethod(ref StringBuilder stringBuilder, INamedTypeSymbol classSymbol)
|
||||
{
|
||||
string fullTypeName = classSymbol.ToDisplayString();
|
||||
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine(" /// <summary>");
|
||||
stringBuilder.AppendLine(" /// Constructs a new component of the specified class, and attaches it to the specified actor.");
|
||||
stringBuilder.AppendLine(" /// </summary>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"owner\">The actor to attach the component to.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"bManualAttachment\">If true, the component will not be attached to the actor's root component.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"relativeTransform\">The relative transform of the component to the actor.</param>");
|
||||
stringBuilder.AppendLine(" /// <returns>The constructed component.</returns>");
|
||||
stringBuilder.AppendLine($" public static {fullTypeName} Construct(UnrealSharp.Engine.AActor owner, bool bManualAttachment, FTransform relativeTransform)");
|
||||
stringBuilder.AppendLine(" {");
|
||||
stringBuilder.AppendLine($" return owner.AddComponentByClass<{fullTypeName}>(bManualAttachment, relativeTransform);");
|
||||
stringBuilder.AppendLine(" }");
|
||||
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine(" /// <summary>");
|
||||
stringBuilder.AppendLine(" /// Constructs a new component of the specified class, and attaches it to the specified actor.");
|
||||
stringBuilder.AppendLine(" /// </summary>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"owner\">The actor to attach the component to.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"componentClass\">The class of the component to construct.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"bManualAttachment\">If true, the component will not be attached to the actor's root component.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"relativeTransform\">The relative transform of the component to the actor.</param>");
|
||||
stringBuilder.AppendLine(" /// <returns>The constructed component.</returns>");
|
||||
stringBuilder.AppendLine($" public static {fullTypeName} Construct(UnrealSharp.Engine.AActor owner, TSubclassOf<UActorComponent> componentClass, bool bManualAttachment, FTransform relativeTransform)");
|
||||
stringBuilder.AppendLine(" {");
|
||||
stringBuilder.AppendLine($" return ({fullTypeName}) owner.AddComponentByClass(componentClass, bManualAttachment, relativeTransform);");
|
||||
stringBuilder.AppendLine(" }");
|
||||
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine(" /// <summary>");
|
||||
stringBuilder.AppendLine(" /// Constructs a new component of the specified class, and attaches it to the specified actor.");
|
||||
stringBuilder.AppendLine(" /// </summary>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"owner\">The actor to attach the component to.</param>");
|
||||
stringBuilder.AppendLine(" /// <returns>The constructed component.</returns>");
|
||||
stringBuilder.AppendLine($" public static {fullTypeName} Construct(UnrealSharp.Engine.AActor owner)");
|
||||
stringBuilder.AppendLine(" {");
|
||||
stringBuilder.AppendLine($" return ({fullTypeName}) owner.AddComponentByClass(typeof({fullTypeName}), false, new FTransform());");
|
||||
stringBuilder.AppendLine(" }");
|
||||
}
|
||||
|
||||
private void GenerateComponentGetter(ref StringBuilder stringBuilder, INamedTypeSymbol classSymbol)
|
||||
{
|
||||
string fullTypeName = classSymbol.ToDisplayString();
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine(" /// <summary>");
|
||||
stringBuilder.AppendLine(" /// Gets the component of the specified class attached to the specified actor.");
|
||||
stringBuilder.AppendLine(" /// </summary>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"owner\">The actor to get the component from.</param>");
|
||||
stringBuilder.AppendLine(" /// <returns>The component if found, otherwise null.</returns>");
|
||||
stringBuilder.AppendLine($" public static new {fullTypeName}? Get(UnrealSharp.Engine.AActor owner)");
|
||||
stringBuilder.AppendLine(" {");
|
||||
stringBuilder.AppendLine($" UActorComponent? foundComponent = owner.GetComponentByClass<{fullTypeName}>(typeof({fullTypeName}));");
|
||||
stringBuilder.AppendLine(" if (foundComponent != null)");
|
||||
stringBuilder.AppendLine(" {");
|
||||
stringBuilder.AppendLine($" return ({fullTypeName}) foundComponent;");
|
||||
stringBuilder.AppendLine(" }");
|
||||
stringBuilder.AppendLine(" return null;");
|
||||
stringBuilder.AppendLine(" }");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace UnrealSharp.ExtensionSourceGenerators;
|
||||
|
||||
public class ActorExtensionGenerator : ExtensionGenerator
|
||||
{
|
||||
public override void Generate(ref StringBuilder builder, INamedTypeSymbol classSymbol)
|
||||
{
|
||||
GenerateSpawnMethod(ref builder, classSymbol);
|
||||
GenerateGetActorsOfClassMethod(ref builder, classSymbol);
|
||||
}
|
||||
|
||||
private void GenerateSpawnMethod(ref StringBuilder stringBuilder, INamedTypeSymbol classSymbol)
|
||||
{
|
||||
string fullTypeName = classSymbol.ToDisplayString();
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine(" /// <summary>");
|
||||
stringBuilder.AppendLine(" /// Spawns an actor of the specified class.");
|
||||
stringBuilder.AppendLine(" /// </summary>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"worldContextObject\">The object to spawn the actor in.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"actorClass\">The class of the actor to spawn.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"spawnTransform\">The transform to spawn the actor at.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"spawnMethod\">The method to handle collisions when spawning the actor.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"instigator\">The actor that caused the actor to be spawned.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"owner\">The actor that owns the spawned actor.</param>");
|
||||
stringBuilder.AppendLine(" /// <returns>The spawned actor.</returns>");
|
||||
stringBuilder.AppendLine($" public static {fullTypeName} Spawn(TSubclassOf<{fullTypeName}> actorClass = default, FTransform spawnTransform = default, UnrealSharp.Engine.ESpawnActorCollisionHandlingMethod spawnMethod = ESpawnActorCollisionHandlingMethod.Undefined, APawn? instigator = null, AActor? owner = null)");
|
||||
stringBuilder.AppendLine(" {");
|
||||
stringBuilder.AppendLine($" return SpawnActor<{fullTypeName}>(actorClass, spawnTransform, spawnMethod, instigator, owner);");
|
||||
stringBuilder.AppendLine(" }");
|
||||
}
|
||||
|
||||
private void GenerateGetActorsOfClassMethod(ref StringBuilder stringBuilder, INamedTypeSymbol classSymbol)
|
||||
{
|
||||
string fullTypeName = classSymbol.ToDisplayString();
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine(" /// <summary>");
|
||||
stringBuilder.AppendLine(" /// Gets all actors of the specified class in the world.");
|
||||
stringBuilder.AppendLine(" /// </summary>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"worldContextObject\">The object to get the actors from.</param>");
|
||||
stringBuilder.AppendLine(" /// <param name=\"outActors\">The list to store the actors in.</param>");
|
||||
stringBuilder.AppendLine($" public static new void GetAllActorsOfClass(out IList<{fullTypeName}> outActors)");
|
||||
stringBuilder.AppendLine(" {");
|
||||
stringBuilder.AppendLine(" UGameplayStatics.GetAllActorsOfClass(out outActors);");
|
||||
stringBuilder.AppendLine(" }");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace UnrealSharp.ExtensionSourceGenerators;
|
||||
|
||||
[Generator]
|
||||
public class ClassExtenderGenerator : IIncrementalGenerator
|
||||
{
|
||||
private static bool IsA(ITypeSymbol classSymbol, ITypeSymbol otherSymbol)
|
||||
{
|
||||
var currentSymbol = classSymbol.BaseType;
|
||||
|
||||
while (currentSymbol != null)
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(currentSymbol, otherSymbol))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
currentSymbol = currentSymbol.BaseType;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var actorExtensionGenerator = new ActorExtensionGenerator();
|
||||
var actorComponentExtensionGenerator = new ActorComponentExtensionGenerator();
|
||||
|
||||
var syntaxProvider = context.SyntaxProvider.CreateSyntaxProvider<(INamedTypeSymbol Symbol, ExtensionGenerator Generator)>(
|
||||
static (syntaxNode, _) => syntaxNode is ClassDeclarationSyntax { BaseList: not null },
|
||||
(context, _) =>
|
||||
{
|
||||
if (context.SemanticModel.GetTypeInfo(context.Node).Type is not INamedTypeSymbol typeSymbol)
|
||||
{
|
||||
return (null!, null!);
|
||||
}
|
||||
|
||||
if (IsA(typeSymbol, context.SemanticModel.Compilation.GetTypeByMetadataName("UnrealSharp.Engine.AActor")!))
|
||||
{
|
||||
return (typeSymbol, actorExtensionGenerator);
|
||||
}
|
||||
else if (IsA(typeSymbol, context.SemanticModel.Compilation.GetTypeByMetadataName("UnrealSharp.Engine.UActorComponent")!))
|
||||
{
|
||||
return (typeSymbol, actorComponentExtensionGenerator);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (null!, null!);
|
||||
}
|
||||
})
|
||||
.Where(classDecl => classDecl.Symbol != null);
|
||||
|
||||
context.RegisterSourceOutput(syntaxProvider, (outputContext, classDecl) =>
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
stringBuilder.AppendLine("#nullable disable");
|
||||
stringBuilder.AppendLine();
|
||||
|
||||
stringBuilder.AppendLine("using UnrealSharp.Engine;");
|
||||
stringBuilder.AppendLine("using UnrealSharp.CoreUObject;");
|
||||
stringBuilder.AppendLine("using UnrealSharp;");
|
||||
stringBuilder.AppendLine();
|
||||
|
||||
stringBuilder.AppendLine($"namespace {classDecl.Symbol.ContainingNamespace};");
|
||||
stringBuilder.AppendLine();
|
||||
stringBuilder.AppendLine($"public partial class {classDecl.Symbol.Name}");
|
||||
stringBuilder.AppendLine("{");
|
||||
|
||||
classDecl.Generator.Generate(ref stringBuilder, classDecl.Symbol);
|
||||
|
||||
stringBuilder.AppendLine("}");
|
||||
outputContext.AddSource($"{classDecl.Symbol.Name}.generated.extension.cs", SourceText.From(stringBuilder.ToString(), Encoding.UTF8));
|
||||
});
|
||||
}
|
||||
|
||||
private class ClassSyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<ClassDeclarationSyntax> CandidateClasses { get; } = [];
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is ClassDeclarationSyntax { BaseList: not null } classDeclaration)
|
||||
{
|
||||
CandidateClasses.Add(classDeclaration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace UnrealSharp.ExtensionSourceGenerators;
|
||||
|
||||
public abstract class ExtensionGenerator
|
||||
{
|
||||
public abstract void Generate(ref StringBuilder builder, INamedTypeSymbol classSymbol);
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<IsRoslynComponent>true</IsRoslynComponent>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoWarn>$(NoWarn);1570;0649;0169;0108;0109</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,7 @@
|
||||
namespace UnrealSharp.Log;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public class CustomLog(ELogVerbosity verbosity = ELogVerbosity.Display) : Attribute
|
||||
{
|
||||
private ELogVerbosity _verbosity = verbosity;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using UnrealSharp.Binds;
|
||||
|
||||
namespace UnrealSharp.Log;
|
||||
|
||||
[NativeCallbacks]
|
||||
public static unsafe partial class FMsgExporter
|
||||
{
|
||||
public static delegate* unmanaged<char*, ELogVerbosity, char*, void> Log;
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
namespace UnrealSharp.Log;
|
||||
|
||||
public enum ELogVerbosity : byte
|
||||
{
|
||||
/** Not used */
|
||||
NoLogging = 0,
|
||||
|
||||
/** Always prints a fatal error to console (and log file) and crashes (even if logging is disabled) */
|
||||
Fatal,
|
||||
|
||||
/**
|
||||
* Prints an error to console (and log file).
|
||||
* Commandlets and the editor collect and report errors. Error messages result in commandlet failure.
|
||||
*/
|
||||
Error,
|
||||
|
||||
/**
|
||||
* Prints a warning to console (and log file).
|
||||
* Commandlets and the editor collect and report warnings. Warnings can be treated as an error.
|
||||
*/
|
||||
Warning,
|
||||
|
||||
/** Prints a message to console (and log file) */
|
||||
Display,
|
||||
|
||||
/** Prints a message to a log file (does not print to console) */
|
||||
Log,
|
||||
|
||||
/**
|
||||
* Prints a verbose message to a log file (if Verbose logging is enabled for the given category,
|
||||
* usually used for detailed logging)
|
||||
*/
|
||||
Verbose,
|
||||
|
||||
/**
|
||||
* Prints a verbose message to a log file (if VeryVerbose logging is enabled,
|
||||
* usually used for detailed logging that would otherwise spam output)
|
||||
*/
|
||||
VeryVerbose,
|
||||
|
||||
// Log masks and special Enum values
|
||||
|
||||
All = VeryVerbose,
|
||||
NumVerbosity,
|
||||
VerbosityMask = 0xf,
|
||||
SetColor = 0x40, // not actually a verbosity, used to set the color of an output device
|
||||
BreakOnLog = 0x80
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
namespace UnrealSharp.Log;
|
||||
|
||||
public static class UnrealLogger
|
||||
{
|
||||
public static void Log(string logName, string message, ELogVerbosity logVerbosity = ELogVerbosity.Display)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (char* logNamePtr = logName)
|
||||
fixed (char* stringPtr = message)
|
||||
{
|
||||
FMsgExporter.CallLog(logNamePtr, logVerbosity, stringPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogWarning(string logName, string message)
|
||||
{
|
||||
Log(logName, message, ELogVerbosity.Warning);
|
||||
}
|
||||
|
||||
public static void LogError(string logName, string message)
|
||||
{
|
||||
Log(logName, message, ELogVerbosity.Error);
|
||||
}
|
||||
|
||||
public static void LogFatal(string logName, string message)
|
||||
{
|
||||
Log(logName, message, ELogVerbosity.Fatal);
|
||||
}
|
||||
|
||||
public static void LogVerbose(string logName, string message)
|
||||
{
|
||||
Log(logName, message, ELogVerbosity.Verbose);
|
||||
}
|
||||
|
||||
public static void LogVeryVerbose(string logName, string message)
|
||||
{
|
||||
Log(logName, message, ELogVerbosity.VeryVerbose);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UnrealSharp.Binds\UnrealSharp.Binds.csproj" />
|
||||
<ProjectReference Include="..\UnrealSharp.SourceGenerators\UnrealSharp.SourceGenerators.csproj">
|
||||
<OutputItemType>Analyzer</OutputItemType>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,6 @@
|
||||
using UnrealSharp.Log;
|
||||
|
||||
namespace UnrealSharp.Plugins;
|
||||
|
||||
[CustomLog]
|
||||
public static partial class LogUnrealSharpPlugins;
|
||||
@ -0,0 +1,40 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Build.Locator;
|
||||
using UnrealSharp.Binds;
|
||||
using UnrealSharp.Core;
|
||||
using UnrealSharp.Shared;
|
||||
|
||||
namespace UnrealSharp.Plugins;
|
||||
|
||||
public static class Main
|
||||
{
|
||||
internal static DllImportResolver _dllImportResolver = null!;
|
||||
|
||||
[UnmanagedCallersOnly]
|
||||
private static unsafe NativeBool InitializeUnrealSharp(char* workingDirectoryPath, nint assemblyPath, PluginsCallbacks* pluginCallbacks, IntPtr bindsCallbacks, IntPtr managedCallbacks)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
string dotnetSdk = DotNetUtilities.GetLatestDotNetSdkPath();
|
||||
MSBuildLocator.RegisterMSBuildPath(dotnetSdk);
|
||||
#endif
|
||||
|
||||
AppDomain.CurrentDomain.SetData("APP_CONTEXT_BASE_DIRECTORY", new string(workingDirectoryPath));
|
||||
|
||||
// Initialize plugin and managed callbacks
|
||||
*pluginCallbacks = PluginsCallbacks.Create();
|
||||
|
||||
NativeBinds.InitializeNativeBinds(bindsCallbacks);
|
||||
ManagedCallbacks.Initialize(managedCallbacks);
|
||||
|
||||
LogUnrealSharpPlugins.Log("UnrealSharp successfully setup!");
|
||||
return NativeBool.True;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogUnrealSharpPlugins.LogError($"Error initializing UnrealSharp: {ex.Message}");
|
||||
return NativeBool.False;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Loader;
|
||||
using UnrealSharp.Engine.Core.Modules;
|
||||
|
||||
namespace UnrealSharp.Plugins;
|
||||
|
||||
public class Plugin
|
||||
{
|
||||
public Plugin(AssemblyName assemblyName, bool isCollectible, string assemblyPath)
|
||||
{
|
||||
AssemblyName = assemblyName;
|
||||
AssemblyPath = assemblyPath;
|
||||
|
||||
string pluginLoadContextName = assemblyName.Name! + "_AssemblyLoadContext";
|
||||
LoadContext = new PluginLoadContext(pluginLoadContextName, new AssemblyDependencyResolver(assemblyPath), isCollectible);
|
||||
WeakRefLoadContext = new WeakReference(LoadContext);
|
||||
}
|
||||
|
||||
public AssemblyName AssemblyName { get; }
|
||||
public string AssemblyPath;
|
||||
|
||||
public PluginLoadContext? LoadContext { get; private set; }
|
||||
public WeakReference? WeakRefLoadContext { get ; }
|
||||
|
||||
public WeakReference? WeakRefAssembly { get; private set; }
|
||||
public List<IModuleInterface> ModuleInterfaces { get; } = [];
|
||||
|
||||
public bool IsLoadContextAlive
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
get => WeakRefLoadContext != null && WeakRefLoadContext.IsAlive;
|
||||
}
|
||||
|
||||
public bool Load()
|
||||
{
|
||||
if (LoadContext == null || (WeakRefAssembly != null && WeakRefAssembly.IsAlive))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Assembly assembly = LoadContext.LoadFromAssemblyName(AssemblyName);
|
||||
WeakRefAssembly = new WeakReference(assembly);
|
||||
|
||||
Type[] types = assembly.GetTypes();
|
||||
|
||||
foreach (Type type in types)
|
||||
{
|
||||
if (type == typeof(IModuleInterface) || !typeof(IModuleInterface).IsAssignableFrom(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Activator.CreateInstance(type) is not IModuleInterface moduleInterface)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
moduleInterface.StartupModule();
|
||||
ModuleInterfaces.Add(moduleInterface);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void Unload()
|
||||
{
|
||||
ShutdownModule();
|
||||
|
||||
if (LoadContext == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PluginLoadContext.RemoveAssemblyFromCache(AssemblyName.Name);
|
||||
|
||||
LoadContext.Unload();
|
||||
LoadContext = null;
|
||||
WeakRefAssembly = null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void ShutdownModule()
|
||||
{
|
||||
foreach (IModuleInterface moduleInterface in ModuleInterfaces)
|
||||
{
|
||||
moduleInterface.ShutdownModule();
|
||||
}
|
||||
|
||||
ModuleInterfaces.Clear();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using UnrealSharp.Binds;
|
||||
using UnrealSharp.Core;
|
||||
|
||||
namespace UnrealSharp.Plugins;
|
||||
|
||||
public class PluginLoadContext : AssemblyLoadContext
|
||||
{
|
||||
public PluginLoadContext(string assemblyName, AssemblyDependencyResolver resolver, bool isCollectible) : base(assemblyName, isCollectible)
|
||||
{
|
||||
_resolver = resolver;
|
||||
}
|
||||
|
||||
private readonly AssemblyDependencyResolver _resolver;
|
||||
private static readonly Dictionary<string, WeakReference<Assembly>> LoadedAssemblies = new();
|
||||
|
||||
static PluginLoadContext()
|
||||
{
|
||||
AddAssembly(typeof(PluginLoader).Assembly);
|
||||
AddAssembly(typeof(NativeBinds).Assembly);
|
||||
AddAssembly(typeof(UnrealSharpObject).Assembly);
|
||||
AddAssembly(typeof(UnrealSharpModule).Assembly);
|
||||
}
|
||||
|
||||
private static void AddAssembly(Assembly assembly)
|
||||
{
|
||||
LoadedAssemblies[assembly.GetName().Name!] = new WeakReference<Assembly>(assembly);
|
||||
}
|
||||
|
||||
public static void RemoveAssemblyFromCache(string assemblyName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assemblyName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LoadedAssemblies.Remove(assemblyName);
|
||||
}
|
||||
|
||||
protected override Assembly? Load(AssemblyName assemblyName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(assemblyName.Name))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (LoadedAssemblies.TryGetValue(assemblyName.Name, out WeakReference<Assembly>? weakRef) && weakRef.TryGetTarget(out Assembly? cachedAssembly))
|
||||
{
|
||||
return cachedAssembly;
|
||||
}
|
||||
|
||||
string? assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
|
||||
if (string.IsNullOrEmpty(assemblyPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using FileStream assemblyFile = File.Open(assemblyPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
string pdbPath = Path.ChangeExtension(assemblyPath, ".pdb");
|
||||
|
||||
Assembly? loadedAssembly;
|
||||
if (!File.Exists(pdbPath))
|
||||
{
|
||||
loadedAssembly = LoadFromStream(assemblyFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
loadedAssembly = LoadFromStream(assemblyFile, pdbFile);
|
||||
}
|
||||
|
||||
LoadedAssemblies[assemblyName.Name] = new WeakReference<Assembly>(loadedAssembly);
|
||||
return loadedAssembly;
|
||||
}
|
||||
|
||||
protected override nint LoadUnmanagedDll(string unmanagedDllName)
|
||||
{
|
||||
string? libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
|
||||
|
||||
return libraryPath != null ? LoadUnmanagedDllFromPath(libraryPath) : nint.Zero;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace UnrealSharp.Plugins;
|
||||
|
||||
public static class PluginLoader
|
||||
{
|
||||
public static readonly List<Plugin> LoadedPlugins = [];
|
||||
|
||||
public static Assembly? LoadPlugin(string assemblyPath, bool isCollectible)
|
||||
{
|
||||
try
|
||||
{
|
||||
AssemblyName assemblyName = new AssemblyName(Path.GetFileNameWithoutExtension(assemblyPath));
|
||||
|
||||
foreach (Plugin loadedPlugin in LoadedPlugins)
|
||||
{
|
||||
if (!loadedPlugin.IsLoadContextAlive)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (loadedPlugin.WeakRefAssembly?.Target is not Assembly assembly)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (assembly.GetName() != assemblyName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LogUnrealSharpPlugins.Log($"Plugin {assemblyName} is already loaded.");
|
||||
return assembly;
|
||||
}
|
||||
|
||||
Plugin plugin = new Plugin(assemblyName, isCollectible, assemblyPath);
|
||||
if (plugin.Load() && plugin.WeakRefAssembly != null && plugin.WeakRefAssembly.Target is Assembly loadedAssembly)
|
||||
{
|
||||
LoadedPlugins.Add(plugin);
|
||||
LogUnrealSharpPlugins.Log($"Successfully loaded plugin: {assemblyName}");
|
||||
return loadedAssembly;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Failed to load plugin: {assemblyName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogUnrealSharpPlugins.LogError($"An error occurred while loading the plugin: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static WeakReference? RemovePlugin(string assemblyName)
|
||||
{
|
||||
foreach (Plugin loadedPlugin in LoadedPlugins)
|
||||
{
|
||||
// Trying to resolve the weakptr to the assembly here will cause unload issues, so we compare names instead
|
||||
if (!loadedPlugin.IsLoadContextAlive || loadedPlugin.AssemblyName.Name != assemblyName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
loadedPlugin.Unload();
|
||||
LoadedPlugins.Remove(loadedPlugin);
|
||||
return loadedPlugin.WeakRefLoadContext;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool UnloadPlugin(string assemblyPath)
|
||||
{
|
||||
string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
|
||||
WeakReference? assemblyLoadContext = RemovePlugin(assemblyName);
|
||||
|
||||
if (assemblyLoadContext == null)
|
||||
{
|
||||
LogUnrealSharpPlugins.Log($"Plugin {assemblyName} is not loaded or already unloaded.");
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LogUnrealSharpPlugins.Log($"Unloading plugin {assemblyName}...");
|
||||
|
||||
int startTimeMs = Environment.TickCount;
|
||||
bool takingTooLong = false;
|
||||
|
||||
while (assemblyLoadContext.IsAlive)
|
||||
{
|
||||
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
|
||||
GC.WaitForPendingFinalizers();
|
||||
|
||||
if (!assemblyLoadContext.IsAlive)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int elapsedTimeMs = Environment.TickCount - startTimeMs;
|
||||
|
||||
if (!takingTooLong && elapsedTimeMs >= 200)
|
||||
{
|
||||
takingTooLong = true;
|
||||
LogUnrealSharpPlugins.LogError($"Unloading {assemblyName} is taking longer than expected...");
|
||||
}
|
||||
else if (elapsedTimeMs >= 1000)
|
||||
{
|
||||
throw new InvalidOperationException($"Failed to unload {assemblyName}. Possible causes: Strong GC handles, running threads, etc.");
|
||||
}
|
||||
}
|
||||
|
||||
LogUnrealSharpPlugins.Log($"{assemblyName} unloaded successfully!");
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogUnrealSharpPlugins.LogError($"An error occurred while unloading the plugin: {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Plugin? FindPluginByName(string assemblyName)
|
||||
{
|
||||
foreach (Plugin loadedPlugin in LoadedPlugins)
|
||||
{
|
||||
if (loadedPlugin.AssemblyName.Name == assemblyName)
|
||||
{
|
||||
return loadedPlugin;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user