---@class HotfixCodeGenHandler 热更层代码生成器
local HotfixCodeGenHandler = {}
--- 执行生成热更层代码
---@param handler CS.FairyEditor.PublishHandler
---@param codeGenConfig CodeGenConfig
function HotfixCodeGenHandler.Do(handler, codeGenConfig)
local codePkgName = handler:ToFilename(handler.pkg.name); --convert chinese to pinyin, remove special chars etc.
--- 从自定义配置中读取路径和命名空间
local exportCodePath = codeGenConfig.HotfixCodeOutPutPath .. '/' .. codePkgName
local namespaceName = codeGenConfig.HotfixNameSpace
--- 初始化自定义组件名前缀
local classNamePrefix = codeGenConfig.ClassNamePrefix
--- 初始化自定义成员变量名前缀
local memberVarNamePrefix = codeGenConfig.MemerVarNamePrefix
--- 从FGUI编辑器中读取配置
---@type CS.FairyEditor.GlobalPublishSettings.CodeGenerationConfig
local settings = handler.project:GetSettings("Publish").codeGeneration
local getMemberByName = settings.getMemberByName
--- 所有将要导出的类(当前包的所有设置为导出的组件,以及当前包所有被引用的组件)
---@type CS.FairyEditor.PublishHandler.ClassInfo[]
local classes = handler:CollectClasses(codeGenConfig.CodeStrip, codeGenConfig.CodeStrip, nil)
handler:SetupCodeFolder(exportCodePath, "cs") --check if target folder exists, and delete old files
local classCnt = classes.Count
local writer = CodeWriter.new()
for i = 0, classCnt - 1 do
local classInfo = classes[i]
local members = classInfo.members
writer:reset()
writer:writeln('using FairyGUI;')
writer:writeln('using Cysharp.Threading.Tasks;')
writer:writeln()
writer:writeln('namespace %s', namespaceName)
writer:startBlock()
--- 组装自定义组件前缀
local className = classNamePrefix .. classInfo.className
-- 1
writer:writeln([[public class %sAwakeSystem : AwakeSystem<%s, GObject>
{
public override void Awake(%s self, GObject go)
{
self.Awake(go);
}
}
]], className, className, className)
writer:writeln([[public class %sDestroySystem : DestroySystem<%s>
{
public override void Destroy(%s self)
{
self.Destroy();
}
}
]], className, className, className)
writer:writeln([[public sealed class %s : FUI
{
public const string UIPackageName = "%s";
public const string UIResName = "%s";
///
/// {uiResName}的组件类型(GComponent、GButton、GProcessBar等),它们都是GObject的子类。
///
public %s self;
]], className, codePkgName, classInfo.resName, classInfo.superClassName)
local memberCnt = members.Count
-- 是否为自定义类型组件标记数组
local customComponentFlagsArray = {}
-- 是否为跨包组件标记数组
local crossPackageFlagsArray = {}
for j = 0, memberCnt - 1
do
local memberInfo = members[j]
customComponentFlagsArray[j] = false
crossPackageFlagsArray[j] = false
-- 判断是不是我们自定义类型组件
local typeName = memberInfo.type
for k = 0, classCnt - 1
do
if typeName == classes[k].className
then
typeName = classNamePrefix .. classes[k].className
customComponentFlagsArray[j] = true
break
end
end
-- 判断是不是跨包类型组件
if memberInfo.res ~= nil then
--- 组装自定义组件前缀
typeName = classNamePrefix .. memberInfo.res.name
crossPackageFlagsArray[j] = true
end
--- 组装自定义成员前缀
writer:writeln('\tpublic %s %s;', typeName, memberVarNamePrefix .. memberInfo.varName)
end
writer:writeln('\tpublic const string URL = "ui://%s%s";', handler.pkg.id, classInfo.resId)
writer:writeln()
writer:writeln([[
private static GObject CreateGObject()
{
return UIPackage.CreateObject(UIPackageName, UIResName);
}
private static void CreateGObjectAsync(UIPackage.CreateObjectCallback result)
{
UIPackage.CreateObjectAsync(UIPackageName, UIResName, result);
}
]])
writer:writeln([[
public static %s CreateInstance(Entity parent)
{
return parent.AddChild<%s, GObject>(CreateGObject());
}
]], className, className)
writer:writeln([[
public static UniTask<%s> CreateInstanceAsync(Entity parent)
{
UniTaskCompletionSource<%s> tcs = new UniTaskCompletionSource<%s>();
CreateGObjectAsync((go) =>
{
tcs.TrySetResult(parent.AddChild<%s, GObject>(go));
});
return tcs.Task;
}
]], className, className, className, className)
writer:writeln([[
///
/// 仅用于go已经实例化情况下的创建(例如另一个组件引用了此组件)
///
///
///
///
public static %s Create(Entity parent, GObject go)
{
return parent.AddChild<%s, GObject>(go);
}
]], className, className)
writer:writeln([[
///
/// 通过此方法获取的FUI,在Dispose时不会释放GObject,需要自行管理(一般在配合FGUI的Pool机制时使用)。
///
public static %s GetFormPool(Entity domain, GObject go)
{
var fui = go.Get<%s>();
if(fui == null)
{
fui = Create(domain, go);
}
fui.isFromFGUIPool = true;
return fui;
}
]], className, className)
writer:writeln([[
public void Awake(GObject go)
{
if(go == null)
{
return;
}
GObject = go;
if (string.IsNullOrWhiteSpace(Name))
{
Name = Id.ToString();
}
self = (%s)go;
self.Add(this);
var com = go.asCom;
if(com != null)
{
]], classInfo.superClassName)
for j = 0, memberCnt - 1
do
local memberInfo = members[j]
--- 组装自定义成员前缀
local memberVarName = memberVarNamePrefix .. memberInfo.varName
if memberInfo.group == 0
then
if getMemberByName
then
if customComponentFlagsArray[j]
then
--- 组装自定义组件前缀
writer:writeln('\t\t\t%s = %s.Create(this, com.GetChild("%s"));', memberVarName, classNamePrefix .. memberInfo.type, memberInfo.name)
elseif crossPackageFlagsArray[j]
then
--- 组装自定义组件前缀
writer:writeln('\t\t\t%s = %s.Create(this, com.GetChild("%s"));', memberVarName, classNamePrefix .. memberInfo.res.name, memberInfo.name)
else
writer:writeln('\t\t\t%s = (%s)com.GetChild("%s");', memberVarName, memberInfo.type, memberInfo.name)
end
else
if customComponentFlagsArray[j]
then
--- 组装自定义组件前缀
writer:writeln('\t\t\t%s = %s.Create(this, com.GetChildAt(%s));', memberVarName, classNamePrefix .. memberInfo.type, memberInfo.index)
elseif crossPackageFlagsArray[j]
then
--- 组装自定义组件前缀
writer:writeln('\t\t\t%s = %s.Create(this, com.GetChildAt(%s));', memberVarName, classNamePrefix .. memberInfo.res.name, memberInfo.index)
else
writer:writeln('\t\t\t%s = (%s)com.GetChildAt(%s);', memberVarName, memberInfo.type, memberInfo.index)
end
end
elseif memberInfo.group == 1
then
if getMemberByName
then
writer:writeln('\t\t\t%s = com.GetController("%s");', memberVarName, memberInfo.name)
else
writer:writeln('\t\t\t%s = com.GetControllerAt(%s);', memberVarName, memberInfo.index)
end
else
if getMemberByName
then
writer:writeln('\t\t\t%s = com.GetTransition("%s");', memberVarName, memberInfo.name)
else
writer:writeln('\t\t\t%s = com.GetTransitionAt(%s);', memberVarName, memberInfo.index)
end
end
end
writer:writeln('\t\t}')
writer:writeln('\t}')
writer:writeln([[
public override void Destroy()
{
base.Destroy();
self.Remove();
self = null;
]])
for j = 0, memberCnt - 1 do
local memberInfo = members[j]
--- 组装自定义成员前缀
local memberVarName = memberVarNamePrefix .. memberInfo.varName
if memberInfo.group == 0 then
if customComponentFlagsArray[j] or crossPackageFlagsArray[j] then
writer:writeln('\t\t%s.Dispose();', memberVarName)
end
writer:writeln('\t\t%s = null;', memberVarName)
elseif memberInfo.group == 1 then
writer:writeln('\t\t%s = null;', memberVarName)
else
writer:writeln('\t\t%s = null;', memberVarName)
end
end
writer:writeln('\t}')
writer:writeln('}')
writer:endBlock()
writer:save(exportCodePath .. '/' .. className .. '.cs')
end
-- 写入fuipackage
writer:reset()
writer:writeln('namespace %s', namespaceName)
writer:startBlock()
writer:writeln('public static partial class FUIPackage')
writer:startBlock()
writer:writeln('public const string %s = "%s";', codePkgName, codePkgName)
for i = 0, classCnt - 1 do
local classInfo = classes[i]
writer:writeln('public const string %s_%s = "ui://%s/%s";', codePkgName, classInfo.resName, codePkgName, classInfo.resName)
end
writer:endBlock() --class
writer:endBlock() --namespace
local binderPackageName = 'Package' .. codePkgName
writer:save(exportCodePath .. '/' .. binderPackageName .. '.cs')
end
return HotfixCodeGenHandler