Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f9c3a6f

Browse files
Add support for hot reloading
1 parent a978505 commit f9c3a6f

File tree

11 files changed

+696
-309
lines changed

11 files changed

+696
-309
lines changed

‎README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ C++ is the standard language for video games as well as many other fields. By pr
7373
Vector3 position(1.0f, 2.0f, 3.0f);
7474
transform.SetPosition(position);
7575

76-
* No need to reload the Unity editor when changing C++
76+
* Hot reloading: change C++ without restarting the game
7777
* Handle `MonoBehaviour` messages in C++
7878

7979
>
@@ -112,6 +112,8 @@ Almost all projects will see a net performance win by reducing garbage collectio
112112

113113
[Testing and benchmarks article](https://jacksondunstan.com/articles/3952)
114114

115+
[Optimizations article](https://jacksondunstan.com/articles/4311)
116+
115117
# Project Structure
116118

117119
When scripting in C++, C# is used only as a "binding" layer so Unity can call C++ functions and C++ functions can call the Unity API. A code generator is used to generate most of these bindings according to the needs of your project.

‎Unity/Assets/NativeScript/Bindings.cs

Lines changed: 137 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using AOT;
22

33
using System;
4+
using System.Collections;
45
using System.IO;
56
using System.Runtime.InteropServices;
67

@@ -251,30 +252,68 @@ public static void Remove(int handle)
251252
}
252253
}
253254

255+
/// <summary>
256+
/// A reusable version of UnityEngine.WaitForSecondsRealtime to avoid
257+
/// GC allocs
258+
/// </summary>
259+
class ReusableWaitForSecondsRealtime : CustomYieldInstruction
260+
{
261+
private float waitTime;
262+
263+
public float WaitTime
264+
{
265+
set
266+
{
267+
waitTime = Time.realtimeSinceStartup + value;
268+
}
269+
}
270+
271+
public override bool keepWaiting
272+
{
273+
get
274+
{
275+
return Time.realtimeSinceStartup < waitTime;
276+
}
277+
}
278+
279+
public ReusableWaitForSecondsRealtime(float time)
280+
{
281+
WaitTime = time;
282+
}
283+
}
284+
254285
// Name of the plugin when using [DllImport]
255-
const string PluginName = "NativeScript";
286+
const string PLUGIN_NAME = "NativeScript";
256287

257288
// Path to load the plugin from when running inside the editor
258289
#if UNITY_EDITOR_OSX
259-
const string PluginPath = "/Plugins/Editor/NativeScript.bundle/Contents/MacOS/NativeScript";
290+
const string PLUGIN_PATH = "/Plugins/Editor/NativeScript.bundle/Contents/MacOS/NativeScript";
260291
#elif UNITY_EDITOR_LINUX
261-
const string PluginPath = "/Plugins/Editor/libNativeScript.so";
292+
const string PLUGIN_PATH = "/Plugins/Editor/libNativeScript.so";
262293
#elif UNITY_EDITOR_WIN
263-
const string PluginPath = "/Plugins/Editor/NativeScript.dll";
294+
const string PLUGIN_PATH = "/Plugins/Editor/NativeScript.dll";
264295
#endif
265-
296+
297+
enum InitMode : byte
298+
{
299+
FirstBoot,
300+
Reload
301+
}
302+
266303
#if UNITY_EDITOR
267304
// Handle to the C++ DLL
268305
static IntPtr libraryHandle;
269306

270307
delegate void InitDelegate(
271-
int maxManagedObjects,
308+
IntPtr memory,
309+
int memorySize,
310+
InitMode initMode,
272311
IntPtr releaseObject,
273312
IntPtr stringNew,
274313
IntPtr setException,
275314
IntPtr arrayGetLength,
276315
/*BEGIN INIT PARAMS*/
277-
IntPtr unityEngineVector3ConstructorSystemSingle_SystemSingle_SystemSingle,
316+
intmaxManagedObjects,IntPtr unityEngineVector3ConstructorSystemSingle_SystemSingle_SystemSingle,
278317
IntPtr unityEngineVector3PropertyGetMagnitude,
279318
IntPtr unityEngineVector3MethodSetSystemSingle_SystemSingle_SystemSingle,
280319
IntPtr unityEngineVector3Methodop_AdditionUnityEngineVector3_UnityEngineVector3,
@@ -754,13 +793,15 @@ static T GetDelegate<T>(
754793
#else
755794
[DllImport(PluginName)]
756795
static extern void Init(
757-
int maxManagedObjects,
796+
IntPtr memory,
797+
int memorySize,
798+
initMode initMode,
758799
IntPtr releaseObject,
759800
IntPtr stringNew,
760801
IntPtr setException,
761802
IntPtr arrayGetLength,
762803
/*BEGIN INIT PARAMS*/
763-
IntPtr unityEngineVector3ConstructorSystemSingle_SystemSingle_SystemSingle,
804+
intmaxManagedObjects,IntPtr unityEngineVector3ConstructorSystemSingle_SystemSingle_SystemSingle,
764805
IntPtr unityEngineVector3PropertyGetMagnitude,
765806
IntPtr unityEngineVector3MethodSetSystemSingle_SystemSingle_SystemSingle,
766807
IntPtr unityEngineVector3Methodop_AdditionUnityEngineVector3_UnityEngineVector3,
@@ -1416,38 +1457,95 @@ IntPtr systemComponentModelDesignComponentRenameEventHandlerInvoke
14161457
delegate void SystemComponentModelDesignComponentRenameEventHandlerRemoveDelegate(int thisHandle, int delHandle);
14171458
/*END DELEGATE TYPES*/
14181459

1460+
private static readonly string pluginPath = Application.dataPath + PLUGIN_PATH;
14191461
public static Exception UnhandledCppException;
14201462
public static SetCsharpExceptionDelegate SetCsharpException;
1463+
private static IntPtr memory;
1464+
private static int memorySize;
14211465

14221466
/// <summary>
14231467
/// Open the C++ plugin and call its PluginMain()
14241468
/// </summary>
14251469
///
1426-
/// <param name="maxManagedObjects">
1427-
/// Maximum number of simultaneous managed objects that the C++ plugin
1428-
/// uses.
1470+
/// <param name="memorySize">
1471+
/// Number of bytes of memory to make available to the C++ plugin
14291472
/// </param>
1430-
public static void Open(
1431-
int maxManagedObjects)
1473+
public static void Open(int memorySize)
14321474
{
1433-
ObjectStore.Init(maxManagedObjects);
1434-
/*BEGIN STRUCTSTORE INIT CALLS*/
1435-
NativeScript.Bindings.StructStore<UnityEngine.Resolution>.Init(maxManagedObjects);
1475+
/*BEGIN STORE INIT CALLS*/
1476+
NativeScript.Bindings.ObjectStore.Init(1000);
1477+
NativeScript.Bindings.StructStore<UnityEngine.Resolution>.Init(1000);
14361478
NativeScript.Bindings.StructStore<UnityEngine.RaycastHit>.Init(1000);
1437-
NativeScript.Bindings.StructStore<UnityEngine.Playables.PlayableGraph>.Init(maxManagedObjects);
1438-
NativeScript.Bindings.StructStore<UnityEngine.Animations.AnimationMixerPlayable>.Init(maxManagedObjects);
1439-
NativeScript.Bindings.StructStore<System.Collections.Generic.KeyValuePair<string, double>>.Init(maxManagedObjects);
1440-
NativeScript.Bindings.StructStore<UnityEngine.Ray>.Init(maxManagedObjects);
1441-
NativeScript.Bindings.StructStore<UnityEngine.SceneManagement.Scene>.Init(maxManagedObjects);
1442-
NativeScript.Bindings.StructStore<UnityEngine.Playables.PlayableHandle>.Init(maxManagedObjects);
1443-
NativeScript.Bindings.StructStore<UnityEngine.XR.WSA.Input.InteractionSourcePose>.Init(maxManagedObjects);
1444-
/*END STRUCTSTORE INIT CALLS*/
1479+
NativeScript.Bindings.StructStore<UnityEngine.Playables.PlayableGraph>.Init(1000);
1480+
NativeScript.Bindings.StructStore<UnityEngine.Animations.AnimationMixerPlayable>.Init(1000);
1481+
NativeScript.Bindings.StructStore<System.Collections.Generic.KeyValuePair<string, double>>.Init(20);
1482+
NativeScript.Bindings.StructStore<UnityEngine.Ray>.Init(10);
1483+
NativeScript.Bindings.StructStore<UnityEngine.SceneManagement.Scene>.Init(1000);
1484+
NativeScript.Bindings.StructStore<UnityEngine.Playables.PlayableHandle>.Init(1000);
1485+
NativeScript.Bindings.StructStore<UnityEngine.XR.WSA.Input.InteractionSourcePose>.Init(1000);
1486+
/*END STORE INIT CALLS*/
14451487

1488+
Bindings.memorySize = memorySize;
1489+
memory = Marshal.AllocHGlobal(memorySize);
1490+
OpenPlugin(InitMode.FirstBoot);
1491+
}
1492+
1493+
// Reloading requires dynamic loading of the C++ plugin, which is only
1494+
// available in the editor
1495+
#if UNITY_EDITOR
1496+
/// <summary>
1497+
/// Reload the C++ plugin. Its memory is intact and false is passed for
1498+
/// the isFirstBoot parameter of PluginMain().
1499+
/// </summary>
1500+
public static void Reload()
1501+
{
1502+
ClosePlugin();
1503+
OpenPlugin(InitMode.Reload);
1504+
}
1505+
1506+
/// <summary>
1507+
/// Poll the plugin for changes and reload if any are found.
1508+
/// </summary>
1509+
///
1510+
/// <param name="pollTime">
1511+
/// Number of seconds between polls.
1512+
/// </param>
1513+
///
1514+
/// <returns>
1515+
/// Enumerator for this iterator function. Can be passed to
1516+
/// MonoBehaviour.StartCoroutine for easy usage.
1517+
/// </returns>
1518+
public static IEnumerator AutoReload(float pollTime)
1519+
{
1520+
// Get the original time
1521+
long lastWriteTime = File.GetLastWriteTime(pluginPath).Ticks;
1522+
1523+
ReusableWaitForSecondsRealtime poll
1524+
= new ReusableWaitForSecondsRealtime(pollTime);
1525+
do
1526+
{
1527+
// Poll. Reload if the last write time changed.
1528+
long cur = File.GetLastWriteTime(pluginPath).Ticks;
1529+
if (cur != lastWriteTime)
1530+
{
1531+
Debug.Log("reloading at " + DateTime.Now);
1532+
lastWriteTime = cur;
1533+
Reload();
1534+
}
1535+
1536+
// Wait to poll again
1537+
poll.WaitTime = pollTime;
1538+
yield return poll;
1539+
}
1540+
while (true);
1541+
}
1542+
#endif
1543+
1544+
private static void OpenPlugin(InitMode initMode)
1545+
{
14461546
#if UNITY_EDITOR
1447-
14481547
// Open native library
1449-
libraryHandle = OpenLibrary(
1450-
Application.dataPath + PluginPath);
1548+
libraryHandle = OpenLibrary(pluginPath);
14511549
InitDelegate Init = GetDelegate<InitDelegate>(
14521550
libraryHandle,
14531551
"Init");
@@ -1498,17 +1596,18 @@ public static void Open(
14981596
SystemComponentModelDesignComponentRenameEventHandlerNativeInvoke = GetDelegate<SystemComponentModelDesignComponentRenameEventHandlerNativeInvokeDelegate>(libraryHandle, "SystemComponentModelDesignComponentRenameEventHandlerNativeInvoke");
14991597
SetCsharpExceptionSystemNullReferenceException = GetDelegate<SetCsharpExceptionSystemNullReferenceExceptionDelegate>(libraryHandle, "SetCsharpExceptionSystemNullReferenceException");
15001598
/*END MONOBEHAVIOUR GETDELEGATE CALLS*/
1501-
15021599
#endif
1503-
15041600
// Init C++ library
15051601
Init(
1506-
maxManagedObjects,
1602+
memory,
1603+
memorySize,
1604+
initMode,
15071605
Marshal.GetFunctionPointerForDelegate(new ReleaseObjectDelegate(ReleaseObject)),
15081606
Marshal.GetFunctionPointerForDelegate(new StringNewDelegate(StringNew)),
15091607
Marshal.GetFunctionPointerForDelegate(new SetExceptionDelegate(SetException)),
15101608
Marshal.GetFunctionPointerForDelegate(new ArrayGetLengthDelegate(ArrayGetLength)),
15111609
/*BEGIN INIT CALL*/
1610+
1000,
15121611
Marshal.GetFunctionPointerForDelegate(new UnityEngineVector3ConstructorSystemSingle_SystemSingle_SystemSingleDelegate(UnityEngineVector3ConstructorSystemSingle_SystemSingle_SystemSingle)),
15131612
Marshal.GetFunctionPointerForDelegate(new UnityEngineVector3PropertyGetMagnitudeDelegate(UnityEngineVector3PropertyGetMagnitude)),
15141613
Marshal.GetFunctionPointerForDelegate(new UnityEngineVector3MethodSetSystemSingle_SystemSingle_SystemSingleDelegate(UnityEngineVector3MethodSetSystemSingle_SystemSingle_SystemSingle)),
@@ -1781,6 +1880,13 @@ public static void Open(
17811880
/// </summary>
17821881
public static void Close()
17831882
{
1883+
ClosePlugin();
1884+
Marshal.FreeHGlobal(memory);
1885+
memory = IntPtr.Zero;
1886+
}
1887+
1888+
private static void ClosePlugin()
1889+
{
17841890
#if UNITY_EDITOR
17851891
CloseLibrary(libraryHandle);
17861892
libraryHandle = IntPtr.Zero;

‎Unity/Assets/NativeScript/BootScene.unity

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ MonoBehaviour:
139139
m_Script: {fileID: 11500000, guid: 6b5575a60b7c04c7a87ff4e161573c66, type: 3}
140140
m_Name:
141141
m_EditorClassIdentifier:
142-
MaxManagedObjects: 1024
142+
MemorySize: 16777216
143+
AutoReload: 1
144+
AutoReloadPollTime: 1
143145
--- !u!4 &643357610
144146
Transform:
145147
m_ObjectHideFlags: 0

‎Unity/Assets/NativeScript/BootScript.cs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,74 @@ namespace NativeScript
55
/// <summary>
66
/// Script to run at app startup that initializes and runs the native plugin
77
/// </summary>
8+
///
89
/// <author>
910
/// Jackson Dunstan, 2017, http://JacksonDunstan.com
1011
/// </author>
12+
///
1113
/// <license>
1214
/// MIT
1315
/// </license>
14-
class BootScript : MonoBehaviour
16+
publicclass BootScript : MonoBehaviour
1517
{
16-
public int MaxManagedObjects = 1024;
18+
public int MemorySize = 1024 * 1024 * 16;
19+
20+
// Reloading requires dynamic loading of the C++ plugin, which is only
21+
// available in the editor
22+
#if UNITY_EDITOR
23+
public bool AutoReload;
24+
25+
public float AutoReloadPollTime = 1.0f;
26+
private float lastAutoReloadPollTime;
27+
private Coroutine autoReloadCoroutine;
28+
#endif
1729

1830
void Awake()
1931
{
32+
#if UNITY_EDITOR
33+
lastAutoReloadPollTime = AutoReloadPollTime;
34+
#endif
2035
DontDestroyOnLoad(gameObject);
21-
Bindings.Open(MaxManagedObjects);
36+
Bindings.Open(MemorySize);
37+
}
38+
39+
#if UNITY_EDITOR
40+
void Update()
41+
{
42+
if (AutoReload)
43+
{
44+
if (AutoReloadPollTime > 0)
45+
{
46+
// Not started yet. Start.
47+
if (autoReloadCoroutine == null)
48+
{
49+
lastAutoReloadPollTime = AutoReloadPollTime;
50+
autoReloadCoroutine = StartCoroutine(
51+
Bindings.AutoReload(
52+
AutoReloadPollTime));
53+
}
54+
// Poll time changed. Restart.
55+
else if (AutoReloadPollTime != lastAutoReloadPollTime)
56+
{
57+
StopCoroutine(autoReloadCoroutine);
58+
lastAutoReloadPollTime = AutoReloadPollTime;
59+
autoReloadCoroutine = StartCoroutine(
60+
Bindings.AutoReload(
61+
AutoReloadPollTime));
62+
}
63+
}
64+
}
65+
else
66+
{
67+
// Not stopped yet. Stop.
68+
if (autoReloadCoroutine != null)
69+
{
70+
StopCoroutine(autoReloadCoroutine);
71+
autoReloadCoroutine = null;
72+
}
73+
}
2274
}
75+
#endif
2376

2477
void OnApplicationQuit()
2578
{
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using UnityEngine;
2+
using UnityEditor;
3+
4+
namespace NativeScript
5+
{
6+
/// <summary>
7+
/// Menus for the Unity Editor
8+
/// </summary>
9+
///
10+
/// <author>
11+
/// Jackson Dunstan, 2018, http://JacksonDunstan.com
12+
/// </author>
13+
///
14+
/// <license>
15+
/// MIT
16+
/// </license>
17+
public static class EditorMenus
18+
{
19+
[MenuItem("NativeScript/Generate Bindings #%g")]
20+
public static void Generate()
21+
{
22+
GenerateBindings.Generate();
23+
}
24+
25+
[MenuItem("NativeScript/Reload Plugin #%r")]
26+
public static void Reload()
27+
{
28+
Bindings.Reload();
29+
}
30+
}
31+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /