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