diff --git a/CSharp/LuaSTG/.gitignore b/CSharp/LuaSTG/.gitignore new file mode 100644 index 00000000..60b2ea06 --- /dev/null +++ b/CSharp/LuaSTG/.gitignore @@ -0,0 +1,400 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +# but not Directory.Build.rsp, as it configures directory-level build defaults +!Directory.Build.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml \ No newline at end of file diff --git a/CSharp/LuaSTG/LuaSTG.Core/BlendMode.cs b/CSharp/LuaSTG/LuaSTG.Core/BlendMode.cs new file mode 100644 index 00000000..46f1b61b --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/BlendMode.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + public enum BlendMode : byte + { + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/Collision.cs b/CSharp/LuaSTG/LuaSTG.Core/Collision.cs new file mode 100644 index 00000000..fa2743eb --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/Collision.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + /// + /// Stores a collision info. + /// + /// The object which has group of first argument in CheckCollision. + /// The object which has group of second argument in CheckCollision. + public record struct Collision(GameObjectBase Self, GameObjectBase Other) + { + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/DestroyEventArgs.cs b/CSharp/LuaSTG/LuaSTG.Core/DestroyEventArgs.cs new file mode 100644 index 00000000..eae87a28 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/DestroyEventArgs.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + /// + /// Stores a destroy event info. + /// + /// Type of the destroy event. + public record struct DestroyEventArgs(DestroyEventType DestroyEventType) + { + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/DestroyEventType.cs b/CSharp/LuaSTG/LuaSTG.Core/DestroyEventType.cs new file mode 100644 index 00000000..eb97a833 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/DestroyEventType.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + /// + /// Type of the destroy event. + /// + public enum DestroyEventType : int + { + /// + /// Destroy by out-of-bounds. + /// + Bound = 0, + /// + /// Destroy by Del method. + /// + Del = 1, + /// + /// Destroy by Kill method. + /// + Kill = 2, + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/GameObject.cs b/CSharp/LuaSTG/LuaSTG.Core/GameObject.cs new file mode 100644 index 00000000..332ba821 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/GameObject.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + /// + /// Base class for any objects created in CLR. + /// + public class GameObject : GameObjectBase + { + public GameObject() : base() + { + + } + + public override void OnFrame() + { + } + + public override void OnColli(Collision collision) + { + } + + public override void OnDestroy(DestroyEventArgs args) + { + } + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/GameObjectBase.cs b/CSharp/LuaSTG/LuaSTG.Core/GameObjectBase.cs new file mode 100644 index 00000000..54fc32ba --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/GameObjectBase.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + using static LuaSTGAPI; + + /// + /// Base class for any objects created by game engine. + /// + public unsafe abstract class GameObjectBase + { + public enum GameObjectStatus : byte + { + /// + /// Available state + /// + Free = 0, + + /// + /// Normal state, active in object pool. + /// + Active = 1, + + /// + /// End of life cycle. + /// + Dead = 2, + + /// + /// End of life cycle. + /// + Killed = 4, + } + + internal static GameObjectBase?[] _id2Obj = new GameObjectBase[65536]; + + internal IntPtr _nativePtr; + private GameObject* _nativePtrConverted; + + internal GameObjectBase() + { + _nativePtr = GameObject_New(); + _nativePtrConverted = (GameObject*)_nativePtr.ToPointer(); + _id2Obj[GameObject_GetID(_nativePtr)] = this; + } + + internal GameObjectBase(IntPtr nativePtr) + { + _nativePtr = nativePtr; + _nativePtrConverted = (GameObject*)_nativePtr.ToPointer(); + _id2Obj[GameObject_GetID(_nativePtr)] = this; + } + + /// + /// Check whether the current gameobject is active in object pool. + /// + /// for active, otherwise . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsValid() + { + return _nativePtr != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void Detach() + { + _nativePtr = 0; + _nativePtrConverted = null; + } + + /// + /// When override in child class, called each frame when this gameobject is active. + /// + public abstract void OnFrame(); + + /// + /// When override in child class, called when this gameobject is set to be deactivated. + /// + /// Args depends on the call site. + public abstract void OnDestroy(DestroyEventArgs args); + + /// + /// When override in child class, called when a collision detected. + /// + /// Info of the collision. + public abstract void OnColli(Collision collision); + + /// + /// When override in child class, called when this gameobject is being rendered. + /// + public virtual void OnRender() + { + DefaultRenderFunc(); + } + + /// + /// Call default render func for current game object. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void DefaultRenderFunc() + { + ThrowIfInvalid(); + GameObject_DefaultRenderFunc(_nativePtr); + } + + /// + /// Throw an exception if the current gameobject is active in object pool. + /// + /// Exception thrown if not in valid state. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void ThrowIfInvalid() + { + if (!IsValid()) throw new InvalidOperationException("gameobject has been disposed."); + } + + #region properties + + public GameObjectStatus Status + { + get => _nativePtrConverted->status; + } + + public double X + { + get => _nativePtrConverted->x; + set => _nativePtrConverted->x = value; + } + + public double Y + { + get => _nativePtrConverted->y; + set => _nativePtrConverted->y = value; + } + + public double LastX + { + get => _nativePtrConverted->lastx; + } + + public double LastY + { + get => _nativePtrConverted->lasty; + } + + public double DX + { + get => _nativePtrConverted->dx; + } + + public double DY + { + get => _nativePtrConverted->dy; + } + + public double VX + { + get => _nativePtrConverted->vx; + set => _nativePtrConverted->vx = value; + } + + public double VY + { + get => _nativePtrConverted->vy; + set => _nativePtrConverted->vy = value; + } + + public double AX + { + get => _nativePtrConverted->ax; + set => _nativePtrConverted->ax = value; + } + + public double AY + { + get => _nativePtrConverted->ay; + set => _nativePtrConverted->ay = value; + } + + public double MaxVX + { + get => _nativePtrConverted->maxvx; + set => _nativePtrConverted->maxvx = value; + } + + public double MaxVY + { + get => _nativePtrConverted->maxvy; + set => _nativePtrConverted->maxvy = value; + } + + public double MaxV + { + get => _nativePtrConverted->maxv; + set => _nativePtrConverted->maxv = value; + } + + public long Group + { + get => _nativePtrConverted->group; + // TODO: API + //set => ((GameObject*)_nativePtr.ToPointer())->group = value; + } + + public bool Bound + { + get => _nativePtrConverted->Bound; + set => _nativePtrConverted->Bound = value; + } + + public bool Colli + { + get => _nativePtrConverted->Colli; + set => _nativePtrConverted->Colli = value; + } + + public bool Rect + { + get => _nativePtrConverted->Rect; + set => _nativePtrConverted->Rect = value; + } + + public double A + { + get => _nativePtrConverted->a; + set => _nativePtrConverted->a = value; + } + + public double B + { + get => _nativePtrConverted->b; + set => _nativePtrConverted->b = value; + } + + public double Layer + { + get => _nativePtrConverted->layer; + // TODO: API + //set => _nativePtrConverted->layer = value; + } + + public double HScale + { + get => _nativePtrConverted->hscale; + set => _nativePtrConverted->hscale = value; + } + + public double VScale + { + get => _nativePtrConverted->vscale; + set => _nativePtrConverted->vscale = value; + } + + public double Rot + { + get => _nativePtrConverted->rot; + set => _nativePtrConverted->rot = value; + } + + public double Omega + { + get => _nativePtrConverted->omega; + set => _nativePtrConverted->omega = value; + } + + public bool Hide + { + get => _nativePtrConverted->Hide; + set => _nativePtrConverted->Hide = value; + } + + public bool Navi + { + get => _nativePtrConverted->Navi; + set => _nativePtrConverted->Navi = value; + } + + + public long Timer + { + get => _nativePtrConverted->timer; + set => _nativePtrConverted->timer = value; + } + + [StructLayout(LayoutKind.Explicit, Size = 320)] + private struct GameObject + { + [FieldOffset(0)] public IntPtr pUpdatePrev; + [FieldOffset(8)] public IntPtr pUpdateNext; + [FieldOffset(16)] public IntPtr pColliPrev; + [FieldOffset(24)] public IntPtr pColliNext; + + [FieldOffset(32)] public ulong uid; + [FieldOffset(40)] public ulong id; + + [FieldOffset(48)] public double lastx; + [FieldOffset(56)] public double lasty; + [FieldOffset(64)] public double x; + [FieldOffset(72)] public double y; + [FieldOffset(80)] public double dx; + [FieldOffset(88)] public double dy; + [FieldOffset(96)] public double vx; + [FieldOffset(104)] public double vy; + [FieldOffset(112)] public double ax; + [FieldOffset(120)] public double ay; + [FieldOffset(128)] public double maxvx; + [FieldOffset(136)] public double maxvy; + [FieldOffset(144)] public double maxv; + [FieldOffset(152)] public double ag; + + [FieldOffset(160)] public long group; + + [FieldOffset(168)] public double a; + [FieldOffset(176)] public double b; + [FieldOffset(184)] public double col_r; + [FieldOffset(192)] public double layer; + [FieldOffset(200)] public double nextlayer; + [FieldOffset(208)] public double hscale; + [FieldOffset(216)] public double vscale; + [FieldOffset(224)] public double rot; + [FieldOffset(232)] public double omega; + [FieldOffset(240)] public double ani_timer; + + [FieldOffset(248)] public IntPtr res; + [FieldOffset(256)] public IntPtr ps; + + [FieldOffset(264)] public long timer; + + [FieldOffset(272)] public uint vertexcolor; + + [FieldOffset(276)] public BlendMode blendmode; + //[FieldOffset(277)] public GameObjectFeatures features; + [FieldOffset(278)] public GameObjectStatus status; + + // 位域标志位集合 + [FieldOffset(279)] public byte flags; + + // 位域访问属性 + public bool Bound + { + readonly get => (flags & (1 << 0)) != 0; + set + { + if (value) + flags |= (1 << 0); + else + flags &= unchecked((byte)~(1 << 0)); + } + } + + public bool Colli + { + readonly get => (flags & (1 << 1)) != 0; + set + { + if (value) + flags |= (1 << 1); + else + flags &= unchecked((byte)~(1 << 1)); + } + } + + public bool Rect + { + readonly get => (flags & (1 << 2)) != 0; + set + { + if (value) + flags |= (1 << 2); + else + flags &= unchecked((byte)~(1 << 2)); + } + } + + public bool Hide + { + readonly get => (flags & (1 << 3)) != 0; + set + { + if (value) + flags |= (1 << 3); + else + flags &= unchecked((byte)~(1 << 3)); + } + } + + public bool Navi + { + readonly get => (flags & (1 << 4)) != 0; + set + { + if (value) + flags |= (1 << 4); + else + flags &= unchecked((byte)~(1 << 4)); + } + } + + public bool IgnoreSuperpause + { + readonly get => (flags & (1 << 5)) != 0; + set + { + if (value) + flags |= (1 << 5); + else + flags &= unchecked((byte)~(1 << 5)); + } + } + + public bool TouchLastXY + { + readonly get => (flags & (1 << 6)) != 0; + set + { + if (value) + flags |= (1 << 6); + else + flags &= unchecked((byte)~(1 << 6)); + } + } + } + #endregion + } + + public static unsafe partial class LuaSTGAPI + { + // TODO: pass non-zero arg for callback optimization + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static IntPtr GameObject_New() => api.gameObject_New(0); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int GameObject_GetID(IntPtr p) => api.gameObject_GetID(p); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void GameObject_DefaultRenderFunc(IntPtr nativePtr) => api.gameObject_DefaultRenderFunc(nativePtr); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Del(GameObjectBase gameObject, bool killMode = false) + { + api.Del(gameObject._nativePtr, (byte)(killMode ? 1 : 0)); + } + + public static IEnumerable ObjList(long groupID) + { + for (var id = FirstObject(groupID); id >= 0; id = NextObject(groupID, id)) + { + yield return GameObjectBase._id2Obj[id]!; + } + } + + private static int FirstObject(long groupID) => api.FirstObject(groupID); + private static int NextObject(long groupID, int id) => api.NextObject(groupID, id); + + [UnmanagedCallersOnly] + private static void CreateLuaGameObject(IntPtr p) + { + _ = new LuaGameObject(p); + } + + [UnmanagedCallersOnly] + private static void DetachGameObject(ulong id) + { + GameObjectBase._id2Obj[id]!.Detach(); + GameObjectBase._id2Obj[id] = null; + } + + [UnmanagedCallersOnly] + private static void CallOnFrame(ulong id) + { + GameObjectBase._id2Obj[id]!.OnFrame(); + } + + [UnmanagedCallersOnly] + private static void CallOnRender(ulong id) + { + GameObjectBase._id2Obj[id]!.OnRender(); + } + + [UnmanagedCallersOnly] + private static void CallOnDestroy(ulong id, int reason) + { + GameObjectBase._id2Obj[id]!.OnDestroy(new((DestroyEventType)reason)); + } + + [UnmanagedCallersOnly] + private static void CallOnColli(ulong id, ulong otherID) + { + var self = GameObjectBase._id2Obj[id]!; + self.OnColli(new(self, GameObjectBase._id2Obj[otherID]!)); + } + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/ILuaSTGApp.cs b/CSharp/LuaSTG/LuaSTG.Core/ILuaSTGApp.cs new file mode 100644 index 00000000..ce620981 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/ILuaSTGApp.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + /// + /// Represents a LuaSTG application. + /// + public interface ILuaSTGApp + { + /// + /// Method called at the startup of the application. + /// + void GameInit(); + /// + /// Method called each frame. + /// + /// + /// if app has finished its execution. + /// + bool FrameFunc(); + /// + /// Method called after frame for rendering. + /// + void RenderFunc(); + /// + /// Method called when exiting application. + /// + void GameExit(); + /// + /// Method called when application gain focus. + /// + void FocusGainFunc(); + /// + /// Method called when application lose focus. + /// + void FocusLoseFunc(); + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/ILuaSTGAppFactory.cs b/CSharp/LuaSTG/LuaSTG.Core/ILuaSTGAppFactory.cs new file mode 100644 index 00000000..4f1add59 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/ILuaSTGAppFactory.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + /// + /// Represents a factory creating a LuaSTG application. + /// + /// + /// After finished loading assemblies, launching procedure will try to find + /// a type with full name "LuaSTG.LuaSTGAppFactory". + /// if the type is found, it will try to create an object with the parameterless constructor + /// and invoke once to obtain an application. + /// The application object obtained will be a singleton. + /// + public interface ILuaSTGAppFactory + { + /// + /// Get an application instance. + /// + /// An application instance if success, otherwise null. + ILuaSTGApp? GetApplication(); + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/LogLevel.cs b/CSharp/LuaSTG/LuaSTG.Core/LogLevel.cs new file mode 100644 index 00000000..7dfc92fa --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/LogLevel.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + public enum LogLevel + { + Trace = 0, + Debug = 1, + Info = 2, + Warning = 3, + Error = 4, + Critical = 5, + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/LuaGameObject.cs b/CSharp/LuaSTG/LuaSTG.Core/LuaGameObject.cs new file mode 100644 index 00000000..b9052442 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/LuaGameObject.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + /// + /// Gameobject instances created by lua script. This object should not be instantiated by user. + /// + public sealed class LuaGameObject : GameObjectBase + { + internal LuaGameObject(IntPtr nativePtr) : base(nativePtr) + { + + } + + public override sealed void OnFrame() + { + } + + public override void OnRender() + { + } + + public override sealed void OnDestroy(DestroyEventArgs args) + { + } + + public override sealed void OnColli(Collision collision) + { + } + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/LuaSTG.Core.csproj b/CSharp/LuaSTG/LuaSTG.Core/LuaSTG.Core.csproj new file mode 100644 index 00000000..bd44ee5a --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/LuaSTG.Core.csproj @@ -0,0 +1,10 @@ + + + + net8.0 + enable + enable + true + + + diff --git a/CSharp/LuaSTG/LuaSTG.Core/LuaSTGAPI.cs b/CSharp/LuaSTG/LuaSTG.Core/LuaSTGAPI.cs new file mode 100644 index 00000000..d0ed23d5 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/LuaSTGAPI.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + public static unsafe partial class LuaSTGAPI + { + private static ILuaSTGApp? app; + + [UnmanagedCallersOnly] + private static int StartUp(UnmanagedAPI* unmanagedAPI, ManagedAPI* managedAPI) + { + try + { + SaveUnmanagedAPI(unmanagedAPI); + AssignManagedAPI(managedAPI); + LoadAppAssembly(); + + return 0; + } + catch (Exception) + { + return 1; + } + } + + private static void LoadAppAssembly() + { + var currAssembly = typeof(LuaSTGAPI).Assembly; + var dir = Path.GetDirectoryName(currAssembly.Location); + if (dir == null) return; + Assembly mainAssembly = AssemblyLoadContext.GetLoadContext(currAssembly) + ?.LoadFromAssemblyPath(Path.Combine(dir, "LuaSTG.dll")) ?? currAssembly; + + LoadDependencyRecursively(mainAssembly); + + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + var type = asm.GetType("LuaSTG.LuaSTGAppFactory"); + if (type != null) + { + app = (Activator.CreateInstance(type) as ILuaSTGAppFactory)?.GetApplication(); + break; + } + } + } + + private static void LoadDependencyRecursively(Assembly assembly) + { + var resolver = new AssemblyDependencyResolver(assembly.Location); + foreach (var an in assembly.GetReferencedAssemblies()) + { + if (an.Name?.StartsWith("LuaSTG") ?? false) + { + Assembly loaded; + var path = resolver.ResolveAssemblyToPath(an); + if (path != null) + { + loaded = Assembly.LoadFrom(path); + } + else + { + loaded = Assembly.Load(an); + } + LoadDependencyRecursively(loaded); + } + } + } + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/ManagedAPI.cs b/CSharp/LuaSTG/LuaSTG.Core/ManagedAPI.cs new file mode 100644 index 00000000..d52fbd34 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/ManagedAPI.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct ManagedAPI + { + public delegate* unmanaged GameInit; + public delegate* unmanaged FrameFunc; + public delegate* unmanaged RenderFunc; + public delegate* unmanaged GameExit; + public delegate* unmanaged FocusLoseFunc; + public delegate* unmanaged FocusGainFunc; + + public delegate* unmanaged DetachGameObject; + public delegate* unmanaged CreateLuaGameObject; + public delegate* unmanaged CallOnFrame; + public delegate* unmanaged CallOnRender; + public delegate* unmanaged CallOnDestroy; + public delegate* unmanaged CallOnColli; + } + + public static unsafe partial class LuaSTGAPI + { + [UnmanagedCallersOnly] + internal unsafe static void GameInit() + { + app?.GameInit(); + } + + [UnmanagedCallersOnly] + internal unsafe static byte FrameFunc() + { + return (byte)((app?.FrameFunc() ?? false) ? 1 : 0); + } + + [UnmanagedCallersOnly] + internal unsafe static void RenderFunc() + { + app?.RenderFunc(); + } + + [UnmanagedCallersOnly] + internal unsafe static void GameExit() + { + app?.GameExit(); + } + + [UnmanagedCallersOnly] + internal unsafe static void FocusGainFunc() + { + app?.FocusGainFunc(); + } + + [UnmanagedCallersOnly] + internal unsafe static void FocusLoseFunc() + { + app?.FocusLoseFunc(); + } + + private static void AssignManagedAPI(ManagedAPI* managedAPI) + { + managedAPI->GameInit = &GameInit; + managedAPI->FrameFunc = &FrameFunc; + managedAPI->RenderFunc = &RenderFunc; + managedAPI->GameExit = &GameExit; + managedAPI->FocusGainFunc = &FocusGainFunc; + managedAPI->FocusLoseFunc = &FocusLoseFunc; + + managedAPI->CreateLuaGameObject = &CreateLuaGameObject; + managedAPI->DetachGameObject = &DetachGameObject; + managedAPI->CallOnFrame = &CallOnFrame; + managedAPI->CallOnRender = &CallOnRender; + managedAPI->CallOnDestroy = &CallOnDestroy; + managedAPI->CallOnColli = &CallOnColli; + } + } +} diff --git a/CSharp/LuaSTG/LuaSTG.Core/UnmanagedAPI.cs b/CSharp/LuaSTG/LuaSTG.Core/UnmanagedAPI.cs new file mode 100644 index 00000000..42317046 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.Core/UnmanagedAPI.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace LuaSTG.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct UnmanagedAPI + { + public delegate* unmanaged log; + + public delegate* unmanaged gameObject_New; + public delegate* unmanaged gameObject_GetID; + public delegate* unmanaged gameObject_DefaultRenderFunc; + + public delegate* unmanaged Del; + public delegate* unmanaged FirstObject; + public delegate* unmanaged NextObject; + + public delegate* unmanaged beginScene; + public delegate* unmanaged endScene; + public delegate* unmanaged renderClear; + } + + public static unsafe partial class LuaSTGAPI + { + /// + /// Print a log to LuaSTG Engine. + /// + /// Logging level. + /// Logging message. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Log(LogLevel level, string message) + { + IntPtr unmanagedString = Marshal.StringToHGlobalAnsi(message); + api.log((int)level, unmanagedString); + Marshal.FreeHGlobal(unmanagedString); + } + + /// + /// Notify engine to start rendering. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void BeginScene() => api.beginScene(); + + /// + /// Notify engine to finish rendering. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void EndScene() => api.endScene(); + + /// + /// Clear the current render target by the color provided. + /// + /// Alpha value. + /// Red value. + /// Green value. + /// Blue value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void RenderClear(byte a, byte r, byte g, byte b) => api.renderClear(a, r, g, b); + + private static UnmanagedAPI api; + + private static void SaveUnmanagedAPI(UnmanagedAPI* unmanagedAPI) + { + api = *unmanagedAPI; + } + } +} diff --git a/CSharp/LuaSTG/LuaSTG.sln b/CSharp/LuaSTG/LuaSTG.sln new file mode 100644 index 00000000..5a44e912 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35825.156 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaSTG", "LuaSTG\LuaSTG.csproj", "{13D615BA-2A83-4640-9A2A-14E2DE7039AA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaSTG.Core", "LuaSTG.Core\LuaSTG.Core.csproj", "{877B4324-2F7A-4B22-BC28-5B75BA854BFD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {13D615BA-2A83-4640-9A2A-14E2DE7039AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13D615BA-2A83-4640-9A2A-14E2DE7039AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13D615BA-2A83-4640-9A2A-14E2DE7039AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13D615BA-2A83-4640-9A2A-14E2DE7039AA}.Release|Any CPU.Build.0 = Release|Any CPU + {877B4324-2F7A-4B22-BC28-5B75BA854BFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {877B4324-2F7A-4B22-BC28-5B75BA854BFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {877B4324-2F7A-4B22-BC28-5B75BA854BFD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {877B4324-2F7A-4B22-BC28-5B75BA854BFD}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7AE1B659-BD97-4D55-87F7-183145C0C2A4} + EndGlobalSection +EndGlobal diff --git a/CSharp/LuaSTG/LuaSTG/LuaSTG.csproj b/CSharp/LuaSTG/LuaSTG/LuaSTG.csproj new file mode 100644 index 00000000..79d81b85 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG/LuaSTG.csproj @@ -0,0 +1,16 @@ + + + + net8.0 + enable + enable + $(MSBuildProjectDirectory)..\..\..\..\build\amd64\bin\Managed + true + Library + true + + + + + + diff --git a/CSharp/LuaSTG/LuaSTG/LuaSTGApp.cs b/CSharp/LuaSTG/LuaSTG/LuaSTGApp.cs new file mode 100644 index 00000000..b0a4bfef --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG/LuaSTGApp.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using LuaSTG.Core; + +using static LuaSTG.Core.LuaSTGAPI; + +namespace LuaSTG +{ + public class LuaSTGApp : ILuaSTGApp + { + public void GameInit() + { + } + + public bool FrameFunc() + { + return false; + } + + public void RenderFunc() + { + } + + public void GameExit() + { + } + + public void FocusGainFunc() + { + } + + public void FocusLoseFunc() + { + } + } +} diff --git a/CSharp/LuaSTG/LuaSTG/LuaSTGAppFactory.cs b/CSharp/LuaSTG/LuaSTG/LuaSTGAppFactory.cs new file mode 100644 index 00000000..a4648d44 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG/LuaSTGAppFactory.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using LuaSTG.Core; + +namespace LuaSTG +{ + public class LuaSTGAppFactory : ILuaSTGAppFactory + { + public ILuaSTGApp? GetApplication() + { + return new LuaSTGApp(); + } + } +} diff --git a/CSharp/LuaSTG/LuaSTG/Properties/launchSettings.json b/CSharp/LuaSTG/LuaSTG/Properties/launchSettings.json new file mode 100644 index 00000000..b9899be7 --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG/Properties/launchSettings.json @@ -0,0 +1,9 @@ +{ + "profiles": { + "LuaSTGSub": { + "commandName": "Executable", + "executablePath": "../../../build/amd64/bin/LuaSTG.exe", + "workingDirectory": "../../../build/amd64/bin/" + } + } +} \ No newline at end of file diff --git a/CSharp/LuaSTG/LuaSTG/runtimeconfig.template.json b/CSharp/LuaSTG/LuaSTG/runtimeconfig.template.json new file mode 100644 index 00000000..f022b7ff --- /dev/null +++ b/CSharp/LuaSTG/LuaSTG/runtimeconfig.template.json @@ -0,0 +1,3 @@ +{ + "rollForwardOnNoCandidateFx": 2 +} \ No newline at end of file diff --git a/LuaSTG/CMakeLists.txt b/LuaSTG/CMakeLists.txt index 4a38c844..4a0058b3 100644 --- a/LuaSTG/CMakeLists.txt +++ b/LuaSTG/CMakeLists.txt @@ -149,6 +149,11 @@ set(LUASTG_ENGINE_SOURCES LuaSTG/LuaBinding/generated/ColorMember.hpp LuaSTG/LuaBinding/generated/GameObjectMember.cpp LuaSTG/LuaBinding/generated/GameObjectMember.hpp + + LuaSTG/CLRBinding/CLRHost.cpp + LuaSTG/CLRBinding/CLRHost.hpp + LuaSTG/CLRBinding/CLRBinding.cpp + LuaSTG/CLRBinding/CLRBinding.hpp LuaSTG/SteamAPI/SteamAPI.cpp LuaSTG/SteamAPI/SteamAPI.hpp @@ -230,10 +235,16 @@ if (LUASTG_LINK_LUASOCKET) message(STATUS "[LuaSTG] Link: luasocket") endif () +set (CLR_HOST_PATH "C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Host.win-x64/8.0.13/runtimes/win-x64/native") +include_directories (${CLR_HOST_PATH}) +find_library (HOSTFXR NAMES nethost PATHS ${CLR_HOST_PATH}) +target_link_libraries (LuaSTG PRIVATE ${HOSTFXR}) + add_custom_command(TARGET LuaSTG POST_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${CMAKE_BINARY_DIR}/bin/$ COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${CMAKE_BINARY_DIR}/bin/$ COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${CMAKE_BINARY_DIR}/bin/$ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CLR_HOST_PATH}/nethost.dll ${CMAKE_BINARY_DIR}/bin/nethost.dll VERBATIM ) diff --git a/LuaSTG/LuaSTG/AppFrame.cpp b/LuaSTG/LuaSTG/AppFrame.cpp index 408f6425..835be6a3 100644 --- a/LuaSTG/LuaSTG/AppFrame.cpp +++ b/LuaSTG/LuaSTG/AppFrame.cpp @@ -7,6 +7,7 @@ #include "utf8.hpp" #include "resource.h" #include "core/Configuration.hpp" +#include "CLRBinding/CLRBinding.hpp" using namespace luastg; @@ -173,6 +174,18 @@ bool AppFrame::Init()noexcept return false; } + CLR = new CLRHost(L".\\Managed\\net8.0\\LuaSTG.runtimeconfig.json"); + if (!CLR->init()) + { + spdlog::info("[luastg] 初始化coreclr失败"); + return false; + } + + if (!InitCLRBinding(CLR, &clr_fn)) + { + spdlog::info("[luastg] 托管程序集LuaSTG.dll加载失败"); + } + // 加载初始化脚本(可选) if (!OnLoadLaunchScriptAndFiles()) { @@ -193,7 +206,7 @@ bool AppFrame::Init()noexcept spdlog::info("[luastg] 初始化对象池,容量{}", LOBJPOOL_SIZE); try { - m_GameObjectPool = std::make_unique(L); + m_GameObjectPool = std::make_unique(L, &clr_fn); } catch (const std::bad_alloc&) { @@ -252,6 +265,7 @@ bool AppFrame::Init()noexcept if (!SafeCallGlobalFunction(LuaEngine::G_CALLBACK_EngineInit)) { return false; } + clr_fn.GameInit(); return true; } @@ -260,6 +274,7 @@ void AppFrame::Shutdown()noexcept if (L) { SafeCallGlobalFunction(LuaEngine::G_CALLBACK_EngineStop); } + clr_fn.GameExit(); m_GameObjectPool = nullptr; spdlog::info("[luastg] 清空对象池"); @@ -381,6 +396,7 @@ bool AppFrame::onUpdate() result = false; m_pAppModel->requestExit(); } + clr_fn.FocusLoseFunc(); } if (window_active_changed & 0x1) { @@ -396,6 +412,7 @@ bool AppFrame::onUpdate() result = false; m_pAppModel->requestExit(); } + clr_fn.FocusGainFunc(); } if (window_active_changed & 0x4) { @@ -425,6 +442,7 @@ bool AppFrame::onUpdate() } bool tAbort = lua_toboolean(L, -1) != 0; lua_pop(L, 1); + tAbort |= clr_fn.FrameFunc(); if (tAbort) m_pAppModel->requestExit(); m_ResourceMgr.UpdateSound(); @@ -442,6 +460,7 @@ bool AppFrame::onRender() bool result = SafeCallGlobalFunction(LuaEngine::G_CALLBACK_EngineDraw); if (!result) m_pAppModel->requestExit(); + clr_fn.RenderFunc(); GetRenderTargetManager()->EndRenderTargetStack(); diff --git a/LuaSTG/LuaSTG/AppFrame.h b/LuaSTG/LuaSTG/AppFrame.h index 605d97d9..d293ca48 100644 --- a/LuaSTG/LuaSTG/AppFrame.h +++ b/LuaSTG/LuaSTG/AppFrame.h @@ -4,6 +4,8 @@ #include "GameResource/ResourceManager.h" #include "GameObject/GameObjectPool.h" #include "Platform/DirectInput.hpp" +#include "CLRBinding/CLRHost.hpp" +#include "CLRBinding/CLRBinding.hpp" namespace luastg { /// @brief 应用程序状态 @@ -58,6 +60,8 @@ namespace luastg { // Lua虚拟机 lua_State* L = nullptr; + ManagedAPI clr_fn; + // 目标帧率 uint32_t m_target_fps{ 60 }; // 测量值 @@ -69,6 +73,8 @@ namespace luastg { // 输入设备 std::unique_ptr m_DirectInput; + + CLRHost* CLR = nullptr; public: /// @brief 保护模式执行脚本 diff --git a/LuaSTG/LuaSTG/CLRBinding/CLRBinding.cpp b/LuaSTG/LuaSTG/CLRBinding/CLRBinding.cpp new file mode 100644 index 00000000..60c6aeee --- /dev/null +++ b/LuaSTG/LuaSTG/CLRBinding/CLRBinding.cpp @@ -0,0 +1,82 @@ +#include "CLRBinding.hpp" +#include "LuaWrapper.hpp" +#include "AppFrame.h" + +inline core::Graphics::IRenderer* LR2D() { return LAPP.GetAppModel()->getRenderer(); } + +namespace luastg +{ + void CLRBinding::Log(int32_t level, intptr_t str) + { + spdlog::log(static_cast(level), "[CSharp] {}", std::string_view((char*)str)); + } + + intptr_t CLRBinding::GameObject_New(uint32_t callbackMask) + { + return (intptr_t)(GameObjectPool::GetInstance()->CLR_New(callbackMask)); + } + + int32_t CLRBinding::GameObject_GetID(intptr_t p) + { + return ((GameObject*)p)->id; + } + + void CLRBinding::GameObject_DefaultRenderFunc(intptr_t p) + { + ((GameObject*)p)->Render(); + } + + void CLRBinding::Del(intptr_t p, bool kill_mode) + { + GameObjectPool::GetInstance()->Del((GameObject*)p, kill_mode); + } + + int32_t CLRBinding::FirstObject(int64_t group_id) + { + return GameObjectPool::GetInstance()->FirstObject(static_cast(group_id)); + } + + int32_t CLRBinding::NextObject(int64_t group_id, int32_t id) + { + return GameObjectPool::GetInstance()->NextObject(static_cast(group_id), id); + } + + void CLRBinding::BeginScene() + { + LR2D()->beginBatch(); + } + + void CLRBinding::EndScene() + { + LR2D()->endBatch(); + } + + void CLRBinding::RenderClear(uint8_t a, uint8_t r, uint8_t g, uint8_t b) + { + core::Color4B color(r, g, b, a); + LR2D()->clearRenderTarget(color); + } + + bool InitCLRBinding(const CLRHost* host, ManagedAPI* functions) + { + void* fn = nullptr; + if (host->load_assembly_and_get_function_pointer( + L".\\Managed\\net8.0\\LuaSTG.dll", + L"LuaSTG.Core.LuaSTGAPI, LuaSTG.Core", + L"StartUp", + UNMANAGEDCALLERSONLY_METHOD, + &fn) || !fn) + { + return false; + } + + UnmanagedAPI payload{}; + + if (((entry_point_fn)fn)(&payload, functions)) + { + return false; + } + + return true; + } +} diff --git a/LuaSTG/LuaSTG/CLRBinding/CLRBinding.hpp b/LuaSTG/LuaSTG/CLRBinding/CLRBinding.hpp new file mode 100644 index 00000000..3fd2215d --- /dev/null +++ b/LuaSTG/LuaSTG/CLRBinding/CLRBinding.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "CLRBinding/CLRHost.hpp" + +namespace luastg +{ +#define BINDING_METHODS \ + DECLARE_CLR_API(void, Log, (int32_t, intptr_t)) \ + \ + DECLARE_CLR_API(intptr_t, GameObject_New, (uint32_t)) \ + DECLARE_CLR_API(int32_t, GameObject_GetID, (intptr_t)) \ + DECLARE_CLR_API(void, GameObject_DefaultRenderFunc, (intptr_t)) \ + \ + DECLARE_CLR_API(void, Del, (intptr_t, bool)) \ + \ + DECLARE_CLR_API(int32_t, FirstObject, (int64_t)) \ + DECLARE_CLR_API(int32_t, NextObject, (int64_t, int32_t)) \ + \ + DECLARE_CLR_API(void, BeginScene, ()) \ + DECLARE_CLR_API(void, EndScene, ()) \ + DECLARE_CLR_API(void, RenderClear, (uint8_t, uint8_t, uint8_t, uint8_t)) + + class CLRBinding { + public: +#define DECLARE_CLR_API(ReturnType, Name, Params) static ReturnType Name Params; + BINDING_METHODS +#undef DECLARE_CLR_API + }; + + struct UnmanagedAPI { +#define DECLARE_CLR_API(ReturnType, Name, Params) ReturnType (CORECLR_DELEGATE_CALLTYPE* Name) Params; + BINDING_METHODS +#undef DECLARE_CLR_API + + UnmanagedAPI() { +#define DECLARE_CLR_API(ReturnType, Name, Params) Name = CLRBinding::Name; + BINDING_METHODS +#undef DECLARE_CLR_API + } + }; + + struct ManagedAPI + { +#pragma region GameLoop + void (CORECLR_DELEGATE_CALLTYPE* GameInit)(); + bool (CORECLR_DELEGATE_CALLTYPE* FrameFunc)(); + void (CORECLR_DELEGATE_CALLTYPE* RenderFunc)(); + void (CORECLR_DELEGATE_CALLTYPE* GameExit)(); + void (CORECLR_DELEGATE_CALLTYPE* FocusLoseFunc)(); + void (CORECLR_DELEGATE_CALLTYPE* FocusGainFunc)(); +#pragma endregion + void (CORECLR_DELEGATE_CALLTYPE* DetachGameObject)(uint64_t); + void (CORECLR_DELEGATE_CALLTYPE* CreateLuaGameObject)(intptr_t); + void (CORECLR_DELEGATE_CALLTYPE* CallOnFrame)(uint64_t); + void (CORECLR_DELEGATE_CALLTYPE* CallOnRender)(uint64_t); + void (CORECLR_DELEGATE_CALLTYPE* CallOnDestroy)(uint64_t, int32_t); + void (CORECLR_DELEGATE_CALLTYPE* CallOnColli)(uint64_t, uint64_t); + }; + + const int CLR_DESTROY_BOUNDS = 0; + const int CLR_DESTROY_DEL = 1; + const int CLR_DESTROY_KILL = 2; + + typedef int (CORECLR_DELEGATE_CALLTYPE* entry_point_fn)(UnmanagedAPI*, ManagedAPI*); + + bool InitCLRBinding(const CLRHost* host, ManagedAPI* functions); +} \ No newline at end of file diff --git a/LuaSTG/LuaSTG/CLRBinding/CLRHost.cpp b/LuaSTG/LuaSTG/CLRBinding/CLRHost.cpp new file mode 100644 index 00000000..9eef7ac9 --- /dev/null +++ b/LuaSTG/LuaSTG/CLRBinding/CLRHost.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include "windows.h" + +#include "CLRHost.hpp" + +void* luastg::CLRHost::load_library(const char_t* path) +{ + HMODULE h = ::LoadLibraryW(path); + assert(h != nullptr); + return (void*)h; +} + +void* luastg::CLRHost::get_export(void* h, const char* name) +{ + void* f = ::GetProcAddress((HMODULE)h, name); + assert(f != nullptr); + return f; +} + +bool luastg::CLRHost::init_hostfxr() +{ + // Pre-allocate a large buffer for the path to hostfxr + char_t buffer[MAX_PATH]; + size_t buffer_size = sizeof(buffer) / sizeof(char_t); + int rc = get_hostfxr_path(buffer, &buffer_size, nullptr); + if (rc != 0) + return false; + + // Load hostfxr and get desired exports + void* lib = load_library(buffer); + _init_runtime = (hostfxr_initialize_for_runtime_config_fn)get_export(lib, "hostfxr_initialize_for_runtime_config"); + _get_delegate = (hostfxr_get_runtime_delegate_fn)get_export(lib, "hostfxr_get_runtime_delegate"); + _close_context = (hostfxr_close_fn)get_export(lib, "hostfxr_close"); + + return (_init_runtime && _get_delegate && _close_context); +} + +bool luastg::CLRHost::get_dotnet_load_assembly_config(const char_t* config_path) +{ + hostfxr_handle cxt = nullptr; + int rc = _init_runtime(config_path, nullptr, &cxt); + if (rc != 0 || cxt == nullptr) + { + //std::cerr << "Init failed: " << std::hex << std::showbase << rc << std::endl; + _close_context(cxt); + return false; + } + + void* load_assembly_and_get_function_pointer = nullptr; + void* load_assembly = nullptr; + void* get_function_pointer = nullptr; + + rc = _get_delegate( + cxt, + hdt_load_assembly_and_get_function_pointer, + &load_assembly_and_get_function_pointer); + if (rc != 0 || load_assembly_and_get_function_pointer == nullptr) + { + //std::cerr << "Get delegate failed: " << std::hex << std::showbase << rc << std::endl; + return false; + } + + rc = _get_delegate( + cxt, + hdt_load_assembly, + &load_assembly); + if (rc != 0 || load_assembly == nullptr) + { + //std::cerr << "Get delegate failed: " << std::hex << std::showbase << rc << std::endl; + return false; + } + + rc = _get_delegate( + cxt, + hdt_get_function_pointer, + &get_function_pointer); + if (rc != 0 || get_function_pointer == nullptr) + { + //std::cerr << "Get delegate failed: " << std::hex << std::showbase << rc << std::endl; + return false; + } + + _close_context(cxt); + + _load_assembly_and_get_function_pointer = (load_assembly_and_get_function_pointer_fn)load_assembly_and_get_function_pointer; + _load_assembly = (load_assembly_fn)load_assembly; + _get_function_pointer = (get_function_pointer_fn)get_function_pointer; + return true; +} + +luastg::CLRHost::CLRHost(const char_t* config_path) +{ + _init_runtime = nullptr; + _get_delegate = nullptr; + _close_context = nullptr; + + _load_assembly_and_get_function_pointer = nullptr; + _load_assembly = nullptr; + _get_function_pointer = nullptr; + + _config_path = config_path; +} + +bool luastg::CLRHost::init() +{ + return init_hostfxr() && get_dotnet_load_assembly_config(_config_path); +} + +int luastg::CLRHost::load_assembly_and_get_function_pointer( + const char_t* assembly_path, + const char_t* type_name, + const char_t* method_name, + const char_t* delegate_type_name, + /*out*/ void** delegate) const +{ + return _load_assembly_and_get_function_pointer( + assembly_path, + type_name, + method_name, + delegate_type_name, + nullptr, + delegate + ); +} + +int luastg::CLRHost::load_assembly(const char_t* assembly_path) const +{ + return _load_assembly( + assembly_path, + nullptr, + nullptr + ); +} + +int luastg::CLRHost::get_function_pointer( + const char_t* type_name, + const char_t* method_name, + const char_t* delegate_type_name, + /*out*/ void** delegate) const +{ + return _get_function_pointer( + type_name, + method_name, + delegate_type_name, + nullptr, + nullptr, + delegate + ); +} + +luastg::CLRHost::~CLRHost() +{ +} \ No newline at end of file diff --git a/LuaSTG/LuaSTG/CLRBinding/CLRHost.hpp b/LuaSTG/LuaSTG/CLRBinding/CLRHost.hpp new file mode 100644 index 00000000..a714cd4e --- /dev/null +++ b/LuaSTG/LuaSTG/CLRBinding/CLRHost.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +namespace luastg +{ + class CLRHost + { + private: + hostfxr_initialize_for_runtime_config_fn _init_runtime = nullptr; + hostfxr_get_runtime_delegate_fn _get_delegate = nullptr; + hostfxr_close_fn _close_context = nullptr; + + load_assembly_and_get_function_pointer_fn _load_assembly_and_get_function_pointer = nullptr; + load_assembly_fn _load_assembly = nullptr; + get_function_pointer_fn _get_function_pointer = nullptr; + + const char_t* _config_path; + + void* load_library(const char_t* path); + void* get_export(void* h, const char* name); + + bool init_hostfxr(); + bool get_dotnet_load_assembly_config(const char_t* config_path); + public: + CLRHost(const char_t* config_path); + bool init(); + + int load_assembly_and_get_function_pointer( + const char_t* assembly_path, + const char_t* type_name, + const char_t* method_name, + const char_t* delegate_type_name, + /*out*/ void** delegate) const; + int load_assembly(const char_t* assembly_path) const; + int get_function_pointer(const char_t* type_name, + const char_t* method_name, + const char_t* delegate_type_name, + /*out*/ void** delegate) const; + + ~CLRHost(); + }; +} \ No newline at end of file diff --git a/LuaSTG/LuaSTG/GameObject/GameObjectPool.cpp b/LuaSTG/LuaSTG/GameObject/GameObjectPool.cpp index 48f6138f..a9cf77ea 100644 --- a/LuaSTG/LuaSTG/GameObject/GameObjectPool.cpp +++ b/LuaSTG/LuaSTG/GameObject/GameObjectPool.cpp @@ -12,12 +12,13 @@ namespace luastg static GameObjectPool* g_GameObjectPool = nullptr; - GameObjectPool::GameObjectPool(lua_State* pL) + GameObjectPool::GameObjectPool(lua_State* pL, ManagedAPI* clr_fn) { assert(g_GameObjectPool == nullptr); g_GameObjectPool = this; // Lua_State G_L = pL; + CLR_fn = clr_fn; // 初始化对象链表 _ClearLinkList(); m_RenderList.clear(); @@ -216,6 +217,8 @@ namespace luastg // 释放引用的资源 p->ReleaseResource(); + CLR_fn->DetachGameObject(p->id); + GameObject* pRet = _ReleaseObject(p); return pRet; @@ -256,6 +259,13 @@ namespace luastg lua_pop(L, 2); // ??? ot } + int GameObjectPool::Del(lua_State* L, bool kill_mode) noexcept + { + GameObject* p = _ToGameObject(L, 1); + Del(L, p, kill_mode); + return 0; + } + // -------------------------------------------------------------------------------- void GameObjectPool::DebugNextFrame() @@ -350,6 +360,13 @@ namespace luastg m_pCurrentObject = nullptr; #endif // USING_MULTI_GAME_WORLD } +#ifdef USING_MULTI_GAME_WORLD + m_pCurrentObject = p; +#endif // USING_MULTI_GAME_WORLD + CLR_fn->CallOnFrame(p->id); +#ifdef USING_MULTI_GAME_WORLD + m_pCurrentObject = nullptr; +#endif // USING_MULTI_GAME_WORLD p->Update(); } } @@ -367,6 +384,7 @@ namespace luastg m_pCurrentObject = p; #endif // USING_MULTI_GAME_WORLD _GameObjectCallback(L, objects_index, p, LGOBJ_CC_FRAME); + CLR_fn->CallOnFrame(p->id); #ifdef USING_MULTI_GAME_WORLD m_pCurrentObject = nullptr; #endif // USING_MULTI_GAME_WORLD @@ -400,6 +418,7 @@ namespace luastg #endif // USING_MULTI_GAME_WORLD if (p->features.has_callback_render) { _GameObjectCallback(G_L, ot_idx, p, LGOBJ_CC_RENDER); + CLR_fn->CallOnRender(p->id); } else { p->Render(); @@ -499,6 +518,7 @@ namespace luastg m_pCurrentObject = p; #endif // USING_MULTI_GAME_WORLD _GameObjectCallback(L, objects_index, p, LGOBJ_CC_DEL); + CLR_fn->CallOnDestroy(p->id, CLR_DESTROY_BOUNDS); #ifdef USING_MULTI_GAME_WORLD m_pCurrentObject = nullptr; #endif // USING_MULTI_GAME_WORLD @@ -554,6 +574,7 @@ namespace luastg lua_call(L, 2, 0); // ... object class lua_pop(L, 2); // ... #ifdef USING_MULTI_GAME_WORLD + CLR_fn->CallOnDestroy(object->id, CLR_DESTROY_BOUNDS); m_pCurrentObject = nullptr; #endif // USING_MULTI_GAME_WORLD } @@ -595,6 +616,7 @@ namespace luastg lua_rawgeti(L, objects_index, pB->id + 1); // ... object1 class1 colli1 object1 object2 lua_call(L, 2, 0); // ... object1 class1 lua_pop(L, 2); // ... + CLR_fn->CallOnColli(pA->id, pB->id); #ifdef USING_MULTI_GAME_WORLD m_pCurrentObject = nullptr; #endif // USING_MULTI_GAME_WORLD @@ -656,6 +678,7 @@ namespace luastg lua_rawgeti(L, objects_index, object2->id + 1); // ... object1 class1 colli1 object1 object2 lua_call(L, 2, 0); // ... object1 class1 lua_pop(L, 2); // ... + CLR_fn->CallOnColli(object1->id, object2->id); #ifdef USING_MULTI_GAME_WORLD m_pCurrentObject = nullptr; #endif // USING_MULTI_GAME_WORLD @@ -829,6 +852,7 @@ namespace luastg static std::string _name(""); spdlog::debug("[object] new {}-{} (img = {})", p->id, p->uid, p->res ? p->res->GetResName() : _name); #endif + CLR_fn->CreateLuaGameObject((intptr_t)p); return 1; } @@ -844,9 +868,12 @@ namespace luastg _InsertToRenderList(p); _InsertToColliLinkList(p, (size_t)p->group); } - int GameObjectPool::Del(lua_State* L, bool kill_mode) noexcept + void GameObjectPool::Del(GameObject* p, bool kill_mode) noexcept + { + Del(G_L, p, kill_mode); + } + void GameObjectPool::Del(lua_State* L, GameObject* p, bool kill_mode) noexcept { - GameObject* p = _ToGameObject(L, 1); if (p->status == GameObjectStatus::Active) { // 标记为即将回收的状态 @@ -861,7 +888,6 @@ namespace luastg lua_call(L, lua_gettop(L) - 1, 0); // } } - return 0; } int GameObjectPool::IsValid(lua_State* L) noexcept { @@ -1392,4 +1418,57 @@ namespace luastg p->ps->SetEmission((int)std::max(0, luaL_checkinteger(L, 2))); return 0; } + + GameObject* GameObjectPool::CLR_New(uint32_t callbackMask) noexcept + { + // 分配一个对象 + GameObject* p = _AllocObject(); + if (p == nullptr) + { + return nullptr; + } + +#if (defined(_DEBUG) && defined(LuaSTG_enable_GameObjectManager_Debug)) + static std::string _name(""); + spdlog::debug("[object] new {}-{} (img = {})", p->id, p->uid, p->res ? p->res->GetResName() : _name); +#endif + +#ifdef USING_ADVANCE_GAMEOBJECT_CLASS + //TODO: create by arg + //p->luaclass.CheckClassClass(G_L, 1); +#endif // USING_ADVANCE_GAMEOBJECT_CLASS + + lua_getglobal(G_L, "___clr_class"); // class ... + + // 创建对象 table + GetObjectTable(G_L); // class ... ot + lua_createtable(G_L, 3, 0); // class ... ot object + lua_pushvalue(G_L, 1); // class ... ot object class + lua_rawseti(G_L, -2, 1); // class ... ot object + lua_pushinteger(G_L, (lua_Integer)p->id); // class ... ot object id + lua_rawseti(G_L, -2, 2); // class ... ot object + lua_pushlightuserdata(G_L, p); // class ... ot object pGameObject + lua_rawseti(G_L, -2, 3); // class ... ot object + + // 设置对象 metatable + lua_rawgeti(G_L, -2, LOBJPOOL_METATABLE_IDX); // class ... ot object mt + lua_setmetatable(G_L, -2); // class ... ot object + + // 设置到全局表 ot[n] + lua_pushvalue(G_L, -1); // class ... ot object object + lua_rawseti(G_L, -3, (int)p->id + 1); // class ... ot object + + lua_settop(G_L, 0); + +#if (defined(_DEBUG) && defined(LuaSTG_enable_GameObjectManager_Debug)) + static std::string _name(""); + spdlog::debug("[object] new {}-{} (img = {})", p->id, p->uid, p->res ? p->res->GetResName() : _name); +#endif + + return p; + } + GameObjectPool* GameObjectPool::GetInstance() + { + return g_GameObjectPool; + } } diff --git a/LuaSTG/LuaSTG/GameObject/GameObjectPool.h b/LuaSTG/LuaSTG/GameObject/GameObjectPool.h index 8f41c1ae..36e615e8 100644 --- a/LuaSTG/LuaSTG/GameObject/GameObjectPool.h +++ b/LuaSTG/LuaSTG/GameObject/GameObjectPool.h @@ -3,6 +3,7 @@ #include "core/FixedObjectPool.hpp" #include #include +#include "CLRBinding/CLRBinding.hpp" // 对象池信息 #define LOBJPOOL_SIZE 32768 // 最大对象数 //32768(full) //16384(half) @@ -32,6 +33,7 @@ namespace luastg core::FixedObjectPool m_ObjectPool; uint64_t m_iUid = 0; lua_State* G_L = nullptr; + ManagedAPI* CLR_fn; // GameObject List struct _less_render { @@ -111,11 +113,14 @@ namespace luastg void _GameObjectCallback(lua_State* L, int otidx, GameObject* p, int cbidx); + void Del(lua_State* L, GameObject* p, bool kill_mode = false) noexcept; public: void DebugNextFrame(); FrameStatistics DebugGetFrameStatistics(); public: + void Del(GameObject* p, bool kill_mode = false) noexcept; + #ifdef USING_MULTI_GAME_WORLD int PushCurrentObject(lua_State* L) noexcept; #endif // USING_MULTI_GAME_WORLD @@ -321,7 +326,15 @@ namespace luastg static int api_ParticleSetEmission(lua_State* L) noexcept; public: - GameObjectPool(lua_State* pL); + // CORECLR + + /// @brief 创建新对象 + GameObject* CLR_New(uint32_t) noexcept; + + static GameObjectPool* GetInstance(); + + public: + GameObjectPool(lua_State* pL, ManagedAPI* clr_fn); GameObjectPool& operator=(const GameObjectPool&) = delete; GameObjectPool(const GameObjectPool&) = delete; ~GameObjectPool(); diff --git a/engine/embedded-script/luastg/main.lua b/engine/embedded-script/luastg/main.lua index d125547f..79a16c21 100644 --- a/engine/embedded-script/luastg/main.lua +++ b/engine/embedded-script/luastg/main.lua @@ -10,3 +10,16 @@ function GameExit() end function FocusLoseFunc() end function FocusGainFunc() end function EventFunc(event, ...) end + +local function ___clr_no_callback() end +___clr_class = +{ + [1] = ___clr_no_callback, + [2] = ___clr_no_callback, + [3] = ___clr_no_callback, + [4] = ___clr_no_callback, + [5] = ___clr_no_callback, + [6] = ___clr_no_callback, + ["is_class"] = true, + ["default_function"] = 110 +} \ No newline at end of file