using FairyGUI;
using Spine.Unity;
using System;
using System.Collections.Generic;
using UnityEngine;
using XGame.Framework.Asset;
using XGame.Framework.Asyncs;
using XGame.Framework.Interfaces;
using XGame.Framework.UI;
namespace XGame.Framework.FGUI
{
internal partial class FguiModule : IUIModule, IUpdate, ILateUpdate, IUIContextSetter, IDisposable, IUIAssetModuleCreator, IPackageHandle
{
///
/// bitmapFont的shader名字
///
private const string bitmap_font_shader = "XGame/Text Shader";
private UIContext _context;
private IUITree _uitree;
private IAssetModule _assetModule;
///
/// 加载中的
///
private Dictionary _loadingMap;
///
/// 已打开的UI
///
private Dictionary _openedMap;
///
/// 已关闭的UI
///
private Dictionary _closedMap;
///
/// Update 和 LateUpdate用
///
private List _updates;
///
/// 已加载的字体
///
private HashSet _fontNames;
public FguiModule(IAssetModule assetModule, IUITree uitree)
{
_assetModule = assetModule;
_uitree = uitree;
_loadingMap = new Dictionary();
_openedMap = new Dictionary();
_closedMap = new Dictionary();
_updates = new List();
_fontNames = new HashSet();
//Gtween的默认曲线
GTweener.defaultEaseType = EaseType.Linear;
#region 只对PackageItem有效
NTexture.CustomDestroyMethod += RecycleTexture;
NAudioClip.CustomDestroyMethod += RecycleAudio;
#if FAIRYGUI_SPINE
GLoader3D.CustomLoadSpine += OnLoadSpine;
GLoader3D.CustomSpineDestroyMethod += RecycleSpine;
#endif
#endregion
UIObjectFactory.SetLoaderExtension(CreatCustomLoader);
}
public UIContext Context { get => _context; set => _context = value; }
#region 接口实现
Camera IUIModule.Camera => _uitree.Camera;
Canvas IUIModule.GetCanvas(UILayer layer)
{
return _uitree.GetCanvas(layer);
}
IAsync IUIModule.OpenAsync(UIKey uikey, object intent)
{
string key = uikey;
if (_loadingMap.ContainsKey(key))
{
return _loadingMap[key];
}
var asyncGroup = new AsyncGroup();
if (_openedMap.ContainsKey(key))
{
Log.Debug($"重复开启UI:{key}");
}
else if (_closedMap.TryGetValue(key, out var closedView))
{ // 已关闭的UI
asyncGroup.On(_ =>
{
_closedMap.Remove(key);
_uitree.SetAsLastSibling(closedView.Panel);
_openedMap.Add(key, closedView);
closedView.Enable(intent);
FrameworkEvent.Instance.Notify(EventDefine.UI_OPENED, uikey);
});
}
else
{
_loadingMap.Add(key, asyncGroup);
var objLoadAsync = new GObjectFromPackageAsync(uikey.PackageName, uikey.PanelName);
objLoadAsync.Join(asyncGroup);
objLoadAsync.On(_ =>
{
_loadingMap.Remove(key);
var panelObj = objLoadAsync.Result as GComponent;
if (panelObj == null)
{
Log.Error($"UI加载结果为空. UIKey:{uikey}");
return;
}
#if UNITY_EDITOR
panelObj.displayObject.gameObject.name = uikey.PackageName + uikey.PanelName;
#endif
var panel = new FguiPanel(panelObj, GetLayer(uikey.UIViewType));
var view = Activator.CreateInstance(uikey.UIViewType) as IUIView;
var context = _context.Clone();
(context as IUIViewAdapter).Key = uikey;
(view as UIView).Init(context, panel);
_uitree.AddPanel(panel);
_openedMap.Add(key, view);
view.Enable(intent);
FrameworkEvent.Instance.Notify(EventDefine.UI_OPENED, uikey);
});
var pkgLoadAsync = LoadPackageAsync(uikey);
if (pkgLoadAsync != null)
{
pkgLoadAsync.Join(asyncGroup);
pkgLoadAsync.On(_ =>
{
objLoadAsync.Start();
});
}
else
{
objLoadAsync.Start();
}
}
asyncGroup.End();
return asyncGroup;
}
void IUIModule.Close(UIKey uikey, bool isDestroy)
{
var key = uikey.Key;
if (_loadingMap.TryGetValue(key, out var async))
{
_loadingMap.Remove(key);
async.RemoveAll();
return;
}
if (_openedMap.TryGetValue(key, out var uiView))
{
_openedMap.Remove(key);
uiView.Disable();
_uitree.RemovePanel(uiView.Panel);
if (isDestroy)
{
DestroyView(uiView);
}
else
{
_closedMap.Add(key, uiView);
}
FrameworkEvent.Instance.Notify(EventDefine.UI_CLOSED, uikey);
return;
}
if (isDestroy && _closedMap.TryGetValue(key, out uiView))
{
_closedMap.Remove(key);
DestroyView(uiView);
}
}
IAsync IUIModule.Preload(UIKey uikey)
{
var loadAsync = LoadPackageAsync(uikey);
return loadAsync;
}
bool IUIModule.IsOpened(UIKey uikey)
{
return _openedMap.ContainsKey(uikey);
}
void IUpdate.Update(int millisecond)
{
if (_openedMap.Count == 0)
return;
_updates.Clear();
_updates.AddRange(_openedMap.Values);
for (var i = 0; i < _updates.Count;)
{
var view = _updates[i];
view.Update(millisecond);
if (view.Active)
{
i++;
}
else
{
_updates.RemoveAt(i);
}
}
}
void ILateUpdate.LateUpdate(int millisecond)
{
if (_updates.Count == 0)
return;
foreach (var uiview in _updates)
{
uiview.LateUpdate(millisecond);
}
_updates.Clear();
}
void IDisposable.Dispose()
{
_updates.Clear();
foreach (var async in _loadingMap.Values)
{
async.RemoveAll();
}
_loadingMap.Clear();
foreach (var uiview in _closedMap.Values)
{
DestroyView(uiview);
}
_closedMap.Clear();
foreach (var uiview in _openedMap.Values)
{
DestroyView(uiview);
}
_openedMap.Clear();
foreach (var fontName in _fontNames)
{
var font = FontManager.UnregisterFont(fontName);
if (font is DynamicFont dynFont)
{
_assetModule.Recycle(dynFont.nativeFont);
}
font?.Dispose();
}
_fontNames.Clear();
#region 清理FGUI緩存
StageEngine.beingQuit = true;
UIPackage.RemoveAllPackages();
Stage.inst.Dispose();
NTexture.CustomDestroyMethod -= RecycleTexture;
NAudioClip.CustomDestroyMethod -= RecycleAudio;
#if FAIRYGUI_SPINE
GLoader3D.CustomLoadSpine -= OnLoadSpine;
GLoader3D.CustomSpineDestroyMethod -= RecycleSpine;
#endif
UIObjectFactory.Clear();
ClearGloaderAssets();
#endregion
(_uitree as IDisposable)?.Dispose();
(_assetModule as IDisposable)?.Dispose();
_assetModule = null;
_context = null;
}
#endregion
#region IPackageHandle 接口实现
public IGObjectLoadAsync LoadGobjectAsync(UIKey uikey)
{
var objLoadAsync = new GObjectFromPackageAsync(uikey.PackageName, uikey.PanelName);
var pkgLoadAsync = LoadPackageAsync(uikey);
if (pkgLoadAsync != null)
{
pkgLoadAsync.On(_ =>
{
objLoadAsync.Start();
});
}
else
{
objLoadAsync.Start();
}
return objLoadAsync;
}
///
/// 加载UIPackage
///
///
///
public IAsync LoadPackageAsync(UIKey uikey)
{
if (UIPackage.GetByName(uikey.PackageName) == null)
{
var pkgAddressable = FguiUtils.ToDescFileName(uikey.PackageName);
if (_loadingMap.ContainsKey(pkgAddressable))
{
return _loadingMap[pkgAddressable];
}
var pkgLoadAsync = _assetModule.LoadAsync(pkgAddressable);
_loadingMap.Add(pkgAddressable, pkgLoadAsync);
pkgLoadAsync.On(_ =>
{
_loadingMap.Remove(pkgAddressable);
var pkgAsset = pkgLoadAsync.Result;
var descData = pkgAsset?.bytes;
_assetModule.Recycle(pkgAsset);
if (descData == null || descData.Length == 0)
{
Log.Error($"UIPackage 加载失败. UIKey:{uikey}");
return;
}
if (UIPackage.GetByName(uikey.PackageName) == null)
{
UIPackage.AddPackage(descData, string.Empty, LoadPackageItemAsync);
//var pkg = UIPackage.GetByName(uikey.PackageName);
//if (pkg != null && pkg.dependencies != null)
//{
// Log.Debug($"{uikey.PackageName} dependencies:{pkg.dependencies.Length}");
// foreach (var dependency in pkg.dependencies)
// {
// foreach(var item in dependency)
// {
// Log.Debug($"dependency key:{item.Key} value:{item.Value}");
// }
// }
//}
}
});
return pkgLoadAsync;
}
else
{
Log.Info($"UIPackage已加载. UIKey:{uikey}");
return null;
}
}
public IAsync LoadFontAsync(string fontName)
{
if (_fontNames.Contains(fontName))
{
return null;
}
if (_loadingMap.TryGetValue(fontName, out var loading))
{
return loading;
}
var async = _assetModule.LoadAsync(fontName);
_loadingMap.Add(fontName, async);
async.On(_ =>
{
_loadingMap.Remove(fontName);
var result = async.Result;
if (result == null)
return;
_fontNames.Add(fontName);
var fguiFont = new DynamicFont()
{
name = fontName,
nativeFont = result
};
if (result.material != null && result.material.shader.name == bitmap_font_shader)
{ // bitmapfont字体使用自带的shader
fguiFont.shader = bitmap_font_shader;
}
FontManager.RegisterFont(fguiFont);
});
return async;
}
#endregion
IUIAssetModule IUIAssetModuleCreator.Create(UIContext context)
{
return new FguiAssetModule(context, this);
}
#region 内部函数
///
/// 异步加载PackageItem
/// UIPackage的Texture、AudioClip等资源
///
///
///
///
///
private void LoadPackageItemAsync(string name, string extension, System.Type type, PackageItem item)
{
Log.Info($"LoadPackageItemAsync name:{name} extension:{extension} type:{type} itemName:{item.name}");
var addressable = $"{item.owner.name}_{name}";
var loadAsync = _assetModule.LoadAsync(addressable);
loadAsync.On(_ =>
{
var asset = loadAsync.Result;
item.owner.SetItemAsset(item, Convert(asset, type), DestroyMethod.Custom);
});
}
private object Convert(UnityEngine.Object value, Type type)
{
if (value == null)
return default;
if (type.IsInstanceOfType(value))
{
return value;
}
if (type.IsClass)
{
if (type.IsSubclassOf(typeof(Component)))
{
var go = value as GameObject;
if (go != null)
{
return go.GetComponent(type);
}
var component = value as Component;
if (component != null)
{
return component.gameObject.GetComponent(type);
}
}
else if (type == typeof(Sprite) && value is Texture2D texture)
{
object sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
return sprite;
}
}
Log.Error($"UIModuleFGUI object conver type Error. source type:{value.GetType()} name:{value} target:{type}");
return value;
}
private void DestroyView(IUIView view)
{
var uiPanel = (view.Panel as FguiPanel).Panel;
(view as IDisposable).Dispose();
try
{
uiPanel.Dispose();
}
catch(Exception ex)
{
Log.Exception($"FguiModule DestroyView exception.", ex);
}
}
///
/// 只对LoadPackageItemAsync加载的资源有效
///
///
private void RecycleTexture(Texture texture)
{
Log.Info($"RecycleTexture Texture:{texture.name}");
_assetModule.Recycle(texture);
}
///
/// 只对LoadPackageItemAsync加载的资源有效
///
///
private void RecycleAudio(AudioClip audioClip)
{
Log.Info($"RecycleAudio AudioClip:{audioClip.name}");
_assetModule?.Recycle(audioClip);
}
private GLoader CreatCustomLoader()
{
return new CustomLoader(this);
}
private void OnLoadSpine(string addressable, Action action)
{
var loadAsync = _assetModule.LoadAsync(addressable);
loadAsync.On(_ =>
{
var data = loadAsync.Result;
if (data == null)
{
return;
}
action(data);
});
}
private void RecycleSpine(SkeletonDataAsset asset)
{
_assetModule.Recycle(asset);
}
private UILayer GetLayer(Type uiviewType)
{
var layer = UILayer.Normal;
var property = uiviewType.GetProperty("Layer", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.IgnoreCase);
if (property != null)
{
layer = (UILayer)property.GetValue(null);
}
return layer;
}
#endregion
}
}