@nayan-ui/engine 1.0.0-beta.3 → 1.0.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +229 -113
- package/lib/module/assets/AssetLoader.js +1 -1
- package/lib/module/assets/ProceduralCache.js +31 -4
- package/lib/module/assets/font.js +10 -7
- package/lib/module/assets/model.js +41 -19
- package/lib/module/assets/storage.shared.js +2 -0
- package/lib/module/assets/storage.web.js +1 -0
- package/lib/module/assets/texture.js +14 -11
- package/lib/module/index.js +4 -1
- package/lib/module/media/audio.js +6 -3
- package/lib/module/native/BrowserEngine.js +17 -0
- package/lib/module/render/GameView.js +86 -18
- package/lib/module/render/light.js +24 -0
- package/lib/module/render/quality.js +12 -5
- package/lib/module/render/useWorld.js +72 -0
- package/lib/module/world/Scoreboard.js +21 -3
- package/lib/module/world/TransformBatch.js +5 -3
- package/lib/module/world/World.js +27 -13
- package/lib/module/world/color.js +80 -0
- package/lib/module/world/desc.js +6 -5
- package/lib/module/world/effects.js +20 -8
- package/lib/typescript/src/assets/AssetLoader.d.ts +1 -1
- package/lib/typescript/src/assets/ProceduralCache.d.ts +12 -0
- package/lib/typescript/src/assets/model.d.ts +10 -0
- package/lib/typescript/src/index.d.ts +4 -1
- package/lib/typescript/src/native/EngineBridge.d.ts +6 -2
- package/lib/typescript/src/native/spec/NativeNayanEngine.d.ts +29 -0
- package/lib/typescript/src/render/GameView.d.ts +19 -7
- package/lib/typescript/src/render/light.d.ts +12 -0
- package/lib/typescript/src/render/quality.d.ts +4 -2
- package/lib/typescript/src/render/useWorld.d.ts +20 -0
- package/lib/typescript/src/types.d.ts +13 -4
- package/lib/typescript/src/world/Scoreboard.d.ts +12 -2
- package/lib/typescript/src/world/TransformBatch.d.ts +2 -2
- package/lib/typescript/src/world/World.d.ts +10 -2
- package/lib/typescript/src/world/color.d.ts +18 -0
- package/lib/typescript/src/world/effects.d.ts +13 -12
- package/lib/typescript/src/world/types.d.ts +11 -6
- package/lib/web/engine_core_bg.wasm +0 -0
- package/lib/web/index.js +521 -255
- package/lib/web/types/src/assets/AssetLoader.d.ts +1 -1
- package/lib/web/types/src/assets/ProceduralCache.d.ts +12 -0
- package/lib/web/types/src/assets/model.d.ts +10 -0
- package/lib/web/types/src/index.d.ts +4 -1
- package/lib/web/types/src/native/EngineBridge.d.ts +6 -2
- package/lib/web/types/src/native/spec/NativeNayanEngine.d.ts +29 -0
- package/lib/web/types/src/render/GameView.d.ts +19 -7
- package/lib/web/types/src/render/light.d.ts +11 -0
- package/lib/web/types/src/render/quality.d.ts +4 -2
- package/lib/web/types/src/render/useWorld.d.ts +19 -0
- package/lib/web/types/src/types.d.ts +13 -4
- package/lib/web/types/src/world/Scoreboard.d.ts +12 -2
- package/lib/web/types/src/world/TransformBatch.d.ts +2 -2
- package/lib/web/types/src/world/World.d.ts +10 -2
- package/lib/web/types/src/world/color.d.ts +17 -0
- package/lib/web/types/src/world/effects.d.ts +13 -12
- package/lib/web/types/src/world/types.d.ts +11 -6
- package/native/android/src/main/java/com/nayanui/engine/EngineSurfaceView.java +87 -26
- package/native/core/build/EngineCore.xcframework/ios-arm64/libengine_core.a +0 -0
- package/native/core/build/EngineCore.xcframework/ios-arm64-simulator/libengine_core.a +0 -0
- package/native/core/build/android/arm64-v8a/libengine_core.a +0 -0
- package/native/core/build/android/armeabi-v7a/libengine_core.a +0 -0
- package/native/core/build/android/x86/libengine_core.a +0 -0
- package/native/core/build/android/x86_64/libengine_core.a +0 -0
- package/native/core/include/engine_core.h +9 -0
- package/native/cpp/NayanEngineModule.cpp +171 -0
- package/native/cpp/NayanEngineModule.h +30 -0
- package/native/cpp/generated/NayanEngineSpecJSI.h +105 -0
- package/native/ios/NayanEngineSurface.mm +30 -1
- package/package.json +2 -2
- package/src/native/spec/NativeNayanEngine.ts +39 -0
|
@@ -8,19 +8,27 @@ import com.facebook.react.bridge.WritableMap;
|
|
|
8
8
|
import com.facebook.react.uimanager.UIManagerHelper;
|
|
9
9
|
import com.facebook.react.uimanager.events.Event;
|
|
10
10
|
import com.facebook.react.uimanager.events.EventDispatcher;
|
|
11
|
-
import android.graphics.
|
|
11
|
+
import android.graphics.PixelFormat;
|
|
12
|
+
import android.os.Build;
|
|
12
13
|
import android.view.Surface;
|
|
14
|
+
import android.view.SurfaceHolder;
|
|
15
|
+
import android.view.SurfaceView;
|
|
13
16
|
import android.view.MotionEvent;
|
|
14
17
|
import android.view.ScaleGestureDetector;
|
|
15
18
|
import android.view.Choreographer;
|
|
16
|
-
import android.view.TextureView;
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
|
-
* A surface the Rust renderer draws into (Vulkan, or OpenGL ES when Vulkan isn't usable).
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* A surface the Rust renderer draws into (Vulkan, or OpenGL ES when Vulkan isn't usable). JS refers
|
|
22
|
+
* to it by `surfaceId`.
|
|
23
|
+
*
|
|
24
|
+
* A SurfaceView, not a TextureView: its frames go straight to the system compositor (often as a
|
|
25
|
+
* hardware overlay), instead of being copied into the app's own view tree by the UI thread's
|
|
26
|
+
* renderer every frame. With a TextureView each game frame also redraws the React Native window and
|
|
27
|
+
* makes the JS thread wait on the UI thread for a free buffer, which is what made heavy 3D scenes
|
|
28
|
+
* stutter and hang on Android. The surface keeps showing its last frame after the renderer detaches,
|
|
29
|
+
* until the view leaves the window, so navigation's exit animation still shows the game.
|
|
22
30
|
*/
|
|
23
|
-
public class EngineSurfaceView extends
|
|
31
|
+
public class EngineSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Choreographer.FrameCallback {
|
|
24
32
|
private int surfaceId = -1;
|
|
25
33
|
private Surface surface;
|
|
26
34
|
private int width;
|
|
@@ -29,6 +37,33 @@ public class EngineSurfaceView extends TextureView implements TextureView.Surfac
|
|
|
29
37
|
private int drawingMode;
|
|
30
38
|
private int drawingBackground = 0xffffff;
|
|
31
39
|
private boolean drawingTick;
|
|
40
|
+
private boolean clockRunning;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The game's display clock: once per refresh while the surface is attached and on screen, the
|
|
44
|
+
* engine is asked for its next frame on the JS thread (see `deliverFrame`). Unlike React Native's
|
|
45
|
+
* requestAnimationFrame, which can tick at half the display's rate, this follows every vsync; the
|
|
46
|
+
* frame itself is drawn on the engine's render thread.
|
|
47
|
+
*/
|
|
48
|
+
private final Choreographer.FrameCallback clock = new Choreographer.FrameCallback() {
|
|
49
|
+
@Override public void doFrame(long frameTimeNanos) {
|
|
50
|
+
if (!clockRunning) return;
|
|
51
|
+
if (surfaceId >= 0 && surface != null) nativeVsync(surfaceId, frameTimeNanos);
|
|
52
|
+
Choreographer.getInstance().postFrameCallback(this);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
private void startClock() {
|
|
57
|
+
if (!clockRunning && surfaceId >= 0 && surface != null && isShown()) {
|
|
58
|
+
clockRunning = true;
|
|
59
|
+
Choreographer.getInstance().postFrameCallback(clock);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private void stopClock() {
|
|
64
|
+
clockRunning = false;
|
|
65
|
+
Choreographer.getInstance().removeFrameCallback(clock);
|
|
66
|
+
}
|
|
32
67
|
private long lastDrawingEvent;
|
|
33
68
|
private int drawingPointer = -1;
|
|
34
69
|
private boolean pencil;
|
|
@@ -38,7 +73,9 @@ public class EngineSurfaceView extends TextureView implements TextureView.Surfac
|
|
|
38
73
|
|
|
39
74
|
public EngineSurfaceView(Context context) {
|
|
40
75
|
super(context);
|
|
41
|
-
|
|
76
|
+
getHolder().addCallback(this);
|
|
77
|
+
// Frames are opaque: the compositor can skip blending the layer.
|
|
78
|
+
getHolder().setFormat(PixelFormat.OPAQUE);
|
|
42
79
|
density = context.getResources().getDisplayMetrics().density;
|
|
43
80
|
scaleDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
|
44
81
|
@Override public boolean onScale(ScaleGestureDetector detector) {
|
|
@@ -60,10 +97,12 @@ public class EngineSurfaceView extends TextureView implements TextureView.Surfac
|
|
|
60
97
|
}
|
|
61
98
|
|
|
62
99
|
/**
|
|
63
|
-
* Stops the renderer, but leaves the
|
|
64
|
-
* here would replace the game's last frame with an empty screen during the
|
|
100
|
+
* Stops the renderer, but leaves the surface (and its last frame) visible until navigation removes
|
|
101
|
+
* the view. Hiding it here would replace the game's last frame with an empty screen during the
|
|
102
|
+
* exit animation.
|
|
65
103
|
*/
|
|
66
104
|
public void release() {
|
|
105
|
+
stopClock();
|
|
67
106
|
stopDrawing();
|
|
68
107
|
drawingId = -1;
|
|
69
108
|
if (surfaceId >= 0) {
|
|
@@ -73,49 +112,62 @@ public class EngineSurfaceView extends TextureView implements TextureView.Surfac
|
|
|
73
112
|
releaseSurface();
|
|
74
113
|
}
|
|
75
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Games ask for 60 frames a second. A SurfaceView's frames never invalidate the view tree, so on
|
|
117
|
+
* Android 15+ the system otherwise judges the app idle and drops its vsync (and with it React
|
|
118
|
+
* Native's frame callbacks) to 30 or 20 Hz, halving the frame rate and doubling load times.
|
|
119
|
+
*/
|
|
120
|
+
private static final float GAME_FRAME_RATE = 60f;
|
|
121
|
+
|
|
122
|
+
private void requestFrameRate() {
|
|
123
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && surface != null && surface.isValid()) {
|
|
124
|
+
try {
|
|
125
|
+
surface.setFrameRate(GAME_FRAME_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT);
|
|
126
|
+
} catch (IllegalArgumentException | IllegalStateException ignored) {
|
|
127
|
+
// A surface being torn down; the next attach asks again.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (Build.VERSION.SDK_INT >= 35) setRequestedFrameRate(REQUESTED_FRAME_RATE_CATEGORY_HIGH);
|
|
131
|
+
}
|
|
132
|
+
|
|
76
133
|
private void attach() {
|
|
77
134
|
if (surfaceId >= 0 && surface != null && surface.isValid() && width > 0 && height > 0) {
|
|
78
135
|
nativeAttach(surfaceId, surface, width, height);
|
|
136
|
+
requestFrameRate();
|
|
79
137
|
if (drawingId >= 0) nativeDrawingInvalidate(drawingId);
|
|
80
138
|
startDrawing();
|
|
139
|
+
startClock();
|
|
81
140
|
}
|
|
82
141
|
}
|
|
83
142
|
|
|
84
143
|
@Override
|
|
85
|
-
public void
|
|
86
|
-
surface =
|
|
87
|
-
width = w;
|
|
88
|
-
height = h;
|
|
89
|
-
attach();
|
|
144
|
+
public void surfaceCreated(SurfaceHolder holder) {
|
|
145
|
+
surface = holder.getSurface();
|
|
90
146
|
}
|
|
91
147
|
|
|
92
148
|
@Override
|
|
93
|
-
public void
|
|
149
|
+
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
|
|
150
|
+
surface = holder.getSurface();
|
|
94
151
|
width = w;
|
|
95
152
|
height = h;
|
|
96
153
|
attach();
|
|
97
154
|
}
|
|
98
155
|
|
|
99
156
|
@Override
|
|
100
|
-
public
|
|
157
|
+
public void surfaceDestroyed(SurfaceHolder holder) {
|
|
158
|
+
stopClock();
|
|
101
159
|
stopDrawing();
|
|
102
|
-
// Wait for the renderer before Android destroys the
|
|
160
|
+
// Wait for the renderer before Android destroys the surface. Keep the id so a screen covered by
|
|
103
161
|
// another route can attach a new surface when it comes back from the back stack.
|
|
104
162
|
if (surfaceId >= 0) {
|
|
105
163
|
nativeDetach(surfaceId);
|
|
106
164
|
}
|
|
107
165
|
releaseSurface();
|
|
108
|
-
return true;
|
|
109
166
|
}
|
|
110
167
|
|
|
111
|
-
|
|
112
|
-
public void onSurfaceTextureUpdated(SurfaceTexture texture) {}
|
|
113
|
-
|
|
168
|
+
/** The holder owns the surface: only forget it (releasing it here would break the next attach). */
|
|
114
169
|
private void releaseSurface() {
|
|
115
|
-
|
|
116
|
-
surface.release();
|
|
117
|
-
surface = null;
|
|
118
|
-
}
|
|
170
|
+
surface = null;
|
|
119
171
|
}
|
|
120
172
|
|
|
121
173
|
public void setDrawingId(int id) {
|
|
@@ -173,12 +225,19 @@ public class EngineSurfaceView extends TextureView implements TextureView.Surfac
|
|
|
173
225
|
if (focused) startDrawing(); else stopDrawing();
|
|
174
226
|
}
|
|
175
227
|
@Override protected void onDetachedFromWindow() {
|
|
228
|
+
stopClock();
|
|
176
229
|
stopDrawing();
|
|
177
230
|
super.onDetachedFromWindow();
|
|
178
231
|
}
|
|
179
232
|
@Override protected void onWindowVisibilityChanged(int visibility) {
|
|
180
233
|
super.onWindowVisibilityChanged(visibility);
|
|
181
|
-
if (visibility == VISIBLE)
|
|
234
|
+
if (visibility == VISIBLE) {
|
|
235
|
+
startDrawing();
|
|
236
|
+
startClock();
|
|
237
|
+
} else {
|
|
238
|
+
stopDrawing();
|
|
239
|
+
stopClock();
|
|
240
|
+
}
|
|
182
241
|
}
|
|
183
242
|
@Override public boolean onTouchEvent(MotionEvent event) {
|
|
184
243
|
if (drawingId < 0) return super.onTouchEvent(event);
|
|
@@ -231,4 +290,6 @@ public class EngineSurfaceView extends TextureView implements TextureView.Surfac
|
|
|
231
290
|
private static native boolean nativeAttach(int surfaceId, Surface surface, int width, int height);
|
|
232
291
|
|
|
233
292
|
private static native void nativeDetach(int surfaceId);
|
|
293
|
+
|
|
294
|
+
private static native void nativeVsync(int surfaceId, long frameTimeNanos);
|
|
234
295
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -265,6 +265,15 @@ void engine_android_init(void *java_vm, void *context);
|
|
|
265
265
|
// Generates and registers all built-in shape meshes (cube, sphere, etc.) with the renderer.
|
|
266
266
|
void engine_generate_builtin_meshes(void);
|
|
267
267
|
// Vertex / index count of a registered renderer mesh, or 0.
|
|
268
|
+
/* engine_render_frame's arguments, but the frame is copied and drawn on the render thread; returns at once with the
|
|
269
|
+
* view-projection written. 1 once the surface has presented a frame, 0 before, -1 if the last frame failed. */
|
|
270
|
+
int32_t engine_render_publish(int32_t surface, const double *frame, size_t frame_len, const float *matrices, const float *colors,
|
|
271
|
+
const float *regions, size_t capacity, const uint32_t *ranges, size_t ranges_len, size_t count,
|
|
272
|
+
float *out_view_proj, const World *world);
|
|
273
|
+
/* [frames presented, smoothed render ms] for a surface drawn by the render thread. */
|
|
274
|
+
void engine_render_stats(int32_t surface, double *out);
|
|
275
|
+
/* A core model's geometry (mesh_create, glTF, glyphs) to the renderer, inside Rust; texture < 0 = none. 1, or 0 if unknown. */
|
|
276
|
+
int32_t engine_render_model(uint32_t mesh, int32_t texture, float shine);
|
|
268
277
|
size_t engine_render_mesh_vertex_count(uint32_t mesh);
|
|
269
278
|
size_t engine_render_mesh_index_count(uint32_t mesh);
|
|
270
279
|
// Copies geometry into caller-owned buffers. Returns the actual count.
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
#include <engine_core.h>
|
|
4
4
|
|
|
5
5
|
#include <algorithm>
|
|
6
|
+
#include <atomic>
|
|
6
7
|
#include <cstring>
|
|
8
|
+
#include <mutex>
|
|
9
|
+
#include <unordered_map>
|
|
10
|
+
#include <unordered_set>
|
|
11
|
+
#include <functional>
|
|
12
|
+
#include <thread>
|
|
13
|
+
#include <vector>
|
|
7
14
|
|
|
8
15
|
#ifdef __ANDROID__
|
|
9
16
|
#include <fbjni/fbjni.h>
|
|
@@ -130,8 +137,48 @@ void initAndroid() {
|
|
|
130
137
|
|
|
131
138
|
} // namespace
|
|
132
139
|
|
|
140
|
+
namespace {
|
|
141
|
+
// Display-clock frames. The registry holds no JS values (those stay in the module, on the JS thread):
|
|
142
|
+
// the clock's thread only schedules a call by surface id, and a module torn down by a reload is never
|
|
143
|
+
// called again.
|
|
144
|
+
struct FrameClient {
|
|
145
|
+
std::shared_ptr<CallInvoker> invoker;
|
|
146
|
+
NayanEngineModule *owner;
|
|
147
|
+
std::shared_ptr<std::atomic<bool>> pending;
|
|
148
|
+
};
|
|
149
|
+
std::mutex framesMutex;
|
|
150
|
+
std::unordered_map<int, FrameClient> frameClients;
|
|
151
|
+
std::unordered_set<NayanEngineModule *> liveModules; // guarded by framesMutex
|
|
152
|
+
} // namespace
|
|
153
|
+
|
|
154
|
+
void deliverFrame(int surface, double timeMs) {
|
|
155
|
+
std::shared_ptr<CallInvoker> invoker;
|
|
156
|
+
NayanEngineModule *owner = nullptr;
|
|
157
|
+
std::shared_ptr<std::atomic<bool>> pending;
|
|
158
|
+
{
|
|
159
|
+
std::lock_guard<std::mutex> lock(framesMutex);
|
|
160
|
+
auto it = frameClients.find(surface);
|
|
161
|
+
if (it == frameClients.end() || it->second.pending->exchange(true)) return; // none, or JS hasn't run the last one
|
|
162
|
+
invoker = it->second.invoker;
|
|
163
|
+
owner = it->second.owner;
|
|
164
|
+
pending = it->second.pending;
|
|
165
|
+
}
|
|
166
|
+
invoker->invokeAsync(SchedulerPriority::UserBlockingPriority, [owner, surface, timeMs, pending](jsi::Runtime &rt) {
|
|
167
|
+
pending->store(false);
|
|
168
|
+
{
|
|
169
|
+
std::lock_guard<std::mutex> lock(framesMutex);
|
|
170
|
+
if (liveModules.count(owner) == 0) return;
|
|
171
|
+
}
|
|
172
|
+
owner->callFrame(rt, surface, timeMs); // modules are destroyed on this (JS) thread, so it's still alive here
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
133
176
|
NayanEngineModule::NayanEngineModule(std::shared_ptr<CallInvoker> jsInvoker)
|
|
134
177
|
: NativeNayanEngineCxxSpec(std::move(jsInvoker)) {
|
|
178
|
+
{
|
|
179
|
+
std::lock_guard<std::mutex> lock(framesMutex);
|
|
180
|
+
liveModules.insert(this);
|
|
181
|
+
}
|
|
135
182
|
#ifdef __ANDROID__
|
|
136
183
|
initAndroid();
|
|
137
184
|
#endif
|
|
@@ -139,9 +186,45 @@ NayanEngineModule::NayanEngineModule(std::shared_ptr<CallInvoker> jsInvoker)
|
|
|
139
186
|
|
|
140
187
|
// Worlds are freed when the module and every buffer aliasing them are gone.
|
|
141
188
|
NayanEngineModule::~NayanEngineModule() {
|
|
189
|
+
{
|
|
190
|
+
std::lock_guard<std::mutex> lock(framesMutex);
|
|
191
|
+
liveModules.erase(this);
|
|
192
|
+
for (auto it = frameClients.begin(); it != frameClients.end();) it = it->second.owner == this ? frameClients.erase(it) : std::next(it);
|
|
193
|
+
}
|
|
142
194
|
for (int document : drawings_) engine_drawing_destroy(document);
|
|
143
195
|
}
|
|
144
196
|
|
|
197
|
+
bool NayanEngineModule::hasFrameClock(jsi::Runtime &) {
|
|
198
|
+
#if defined(__ANDROID__) || defined(__APPLE__)
|
|
199
|
+
return true; // EngineSurfaceView's Choreographer / NayanEngineSurfaceView's CADisplayLink call deliverFrame
|
|
200
|
+
#else
|
|
201
|
+
return false;
|
|
202
|
+
#endif
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
void NayanEngineModule::startFrames(jsi::Runtime &rt, double surface, jsi::Function onFrame) {
|
|
206
|
+
int s = static_cast<int>(surface);
|
|
207
|
+
frameCallbacks_.erase(s);
|
|
208
|
+
frameCallbacks_.emplace(s, std::move(onFrame));
|
|
209
|
+
std::lock_guard<std::mutex> lock(framesMutex);
|
|
210
|
+
frameClients[s] = FrameClient{jsInvoker_, this, std::make_shared<std::atomic<bool>>(false)};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
void NayanEngineModule::stopFrames(jsi::Runtime &, double surface) {
|
|
214
|
+
int s = static_cast<int>(surface);
|
|
215
|
+
{
|
|
216
|
+
std::lock_guard<std::mutex> lock(framesMutex);
|
|
217
|
+
auto it = frameClients.find(s);
|
|
218
|
+
if (it != frameClients.end() && it->second.owner == this) frameClients.erase(it);
|
|
219
|
+
}
|
|
220
|
+
frameCallbacks_.erase(s);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
void NayanEngineModule::callFrame(jsi::Runtime &rt, int surface, double timeMs) {
|
|
224
|
+
auto it = frameCallbacks_.find(surface);
|
|
225
|
+
if (it != frameCallbacks_.end()) it->second.call(rt, timeMs);
|
|
226
|
+
}
|
|
227
|
+
|
|
145
228
|
namespace {
|
|
146
229
|
// NaN, fractional and out-of-range ids are unknown (casting them to int is undefined or aliases another id).
|
|
147
230
|
bool knownWorldId(double worldId, int next) {
|
|
@@ -213,6 +296,26 @@ jsi::ArrayBuffer fileData(jsi::Runtime &rt, jsi::Object &data, const char *metho
|
|
|
213
296
|
return data.getArrayBuffer(rt);
|
|
214
297
|
}
|
|
215
298
|
|
|
299
|
+
/** An ArrayBuffer's bytes, copied so a background thread can read them after the JS call returns. */
|
|
300
|
+
std::vector<uint8_t> copyBytes(jsi::Runtime &rt, jsi::Object &data, const char *method) {
|
|
301
|
+
jsi::ArrayBuffer buffer = fileData(rt, data, method);
|
|
302
|
+
const uint8_t *bytes = buffer.data(rt);
|
|
303
|
+
return std::vector<uint8_t>(bytes, bytes + buffer.size(rt));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Runs `job` (which must not touch JSI) on its own native thread and settles a JS promise with its
|
|
308
|
+
* result on the JS thread. Decoding images, glTF models, fonts and sounds this way keeps them off
|
|
309
|
+
* the JS thread, and several loads run in parallel on the phone's cores. The core's asset
|
|
310
|
+
* registries are locked, so concurrent loads are safe.
|
|
311
|
+
*/
|
|
312
|
+
jsi::Value runInBackground(jsi::Runtime &rt, const std::shared_ptr<CallInvoker> &jsInvoker, std::function<double()> job) {
|
|
313
|
+
auto promise = std::make_shared<AsyncPromise<double>>(rt, jsInvoker);
|
|
314
|
+
jsi::Value result(rt, promise->get(rt));
|
|
315
|
+
std::thread([promise, job = std::move(job)]() { promise->resolve(job()); }).detach();
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
|
|
216
319
|
} // namespace
|
|
217
320
|
|
|
218
321
|
double NayanEngineModule::spawnDesc(jsi::Runtime &rt, double world, jsi::Object desc) {
|
|
@@ -438,6 +541,13 @@ double NayanEngineModule::modelLoad(jsi::Runtime &rt, jsi::Object data, bool cen
|
|
|
438
541
|
return engine_model_load(buffer.data(rt), buffer.size(rt), center ? 1 : 0, static_cast<float>(fit)); // copied
|
|
439
542
|
}
|
|
440
543
|
|
|
544
|
+
jsi::Value NayanEngineModule::modelLoadAsync(jsi::Runtime &rt, jsi::Object data, bool center, double fit) {
|
|
545
|
+
auto bytes = std::make_shared<std::vector<uint8_t>>(copyBytes(rt, data, "modelLoadAsync"));
|
|
546
|
+
return runInBackground(rt, jsInvoker_, [bytes, center, fit]() {
|
|
547
|
+
return static_cast<double>(engine_model_load(bytes->data(), bytes->size(), center ? 1 : 0, static_cast<float>(fit)));
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
441
551
|
jsi::Object NayanEngineModule::modelVertices(jsi::Runtime &rt, double mesh) {
|
|
442
552
|
uint32_t m = id(mesh);
|
|
443
553
|
return external(rt, nullptr, engine_model_vertices(m), static_cast<size_t>(engine_model_vertex_count(m)) * 11 * sizeof(float));
|
|
@@ -458,6 +568,11 @@ double NayanEngineModule::modelTexture(jsi::Runtime &, double mesh) {
|
|
|
458
568
|
return engine_model_texture(id(mesh));
|
|
459
569
|
}
|
|
460
570
|
|
|
571
|
+
bool NayanEngineModule::renderModel(jsi::Runtime &, double mesh, double texture, double shine) {
|
|
572
|
+
int32_t t = texture >= 0 && texture <= INT32_MAX ? static_cast<int32_t>(texture) : -1;
|
|
573
|
+
return engine_render_model(id(mesh), t, static_cast<float>(shine)) != 0;
|
|
574
|
+
}
|
|
575
|
+
|
|
461
576
|
double NayanEngineModule::meshAlias(jsi::Runtime &, double base) {
|
|
462
577
|
return engine_mesh_alias(id(base));
|
|
463
578
|
}
|
|
@@ -556,6 +671,11 @@ double NayanEngineModule::textureLoad(jsi::Runtime &rt, jsi::Object data) {
|
|
|
556
671
|
return engine_texture_load(buffer.data(rt), buffer.size(rt)); // decoded and copied
|
|
557
672
|
}
|
|
558
673
|
|
|
674
|
+
jsi::Value NayanEngineModule::textureLoadAsync(jsi::Runtime &rt, jsi::Object data) {
|
|
675
|
+
auto bytes = std::make_shared<std::vector<uint8_t>>(copyBytes(rt, data, "textureLoadAsync"));
|
|
676
|
+
return runInBackground(rt, jsInvoker_, [bytes]() { return static_cast<double>(engine_texture_load(bytes->data(), bytes->size())); });
|
|
677
|
+
}
|
|
678
|
+
|
|
559
679
|
jsi::Object NayanEngineModule::texturePixels(jsi::Runtime &rt, double texture) {
|
|
560
680
|
uint32_t size[2] = {0, 0};
|
|
561
681
|
engine_texture_size(id(texture), size);
|
|
@@ -574,6 +694,15 @@ double NayanEngineModule::fontLoad(jsi::Runtime &rt, jsi::Object data, double de
|
|
|
574
694
|
return engine_font_load(buffer.data(rt), buffer.size(rt), static_cast<float>(depth), utf8.data(), utf8.size(), static_cast<float>(weight));
|
|
575
695
|
}
|
|
576
696
|
|
|
697
|
+
jsi::Value NayanEngineModule::fontLoadAsync(jsi::Runtime &rt, jsi::Object data, double depth, jsi::String chars, double weight) {
|
|
698
|
+
auto bytes = std::make_shared<std::vector<uint8_t>>(copyBytes(rt, data, "fontLoadAsync"));
|
|
699
|
+
auto utf8 = std::make_shared<std::string>(chars.utf8(rt));
|
|
700
|
+
return runInBackground(rt, jsInvoker_, [bytes, utf8, depth, weight]() {
|
|
701
|
+
return static_cast<double>(
|
|
702
|
+
engine_font_load(bytes->data(), bytes->size(), static_cast<float>(depth), utf8->data(), utf8->size(), static_cast<float>(weight)));
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
577
706
|
jsi::Array NayanEngineModule::fontGlyphs(jsi::Runtime &rt, double font) {
|
|
578
707
|
uint32_t f = id(font);
|
|
579
708
|
const float *glyphs = engine_font_glyphs(f);
|
|
@@ -655,6 +784,38 @@ double NayanEngineModule::renderFrame(
|
|
|
655
784
|
r.size(rt) / sizeof(uint32_t), n, out.size(rt) >= 16 * sizeof(float) ? reinterpret_cast<float *>(out.data(rt)) : nullptr, find(world));
|
|
656
785
|
}
|
|
657
786
|
|
|
787
|
+
double NayanEngineModule::publishFrame(
|
|
788
|
+
jsi::Runtime &rt,
|
|
789
|
+
double surface,
|
|
790
|
+
jsi::Object frame,
|
|
791
|
+
jsi::Object matrices,
|
|
792
|
+
jsi::Object colors,
|
|
793
|
+
jsi::Object regions,
|
|
794
|
+
jsi::Object ranges,
|
|
795
|
+
double count,
|
|
796
|
+
jsi::Object viewProj,
|
|
797
|
+
double world) {
|
|
798
|
+
jsi::ArrayBuffer f = fileData(rt, frame, "publishFrame");
|
|
799
|
+
jsi::ArrayBuffer m = fileData(rt, matrices, "publishFrame");
|
|
800
|
+
jsi::ArrayBuffer c = fileData(rt, colors, "publishFrame");
|
|
801
|
+
jsi::ArrayBuffer g = fileData(rt, regions, "publishFrame");
|
|
802
|
+
jsi::ArrayBuffer r = fileData(rt, ranges, "publishFrame");
|
|
803
|
+
jsi::ArrayBuffer out = fileData(rt, viewProj, "publishFrame");
|
|
804
|
+
size_t capacity = std::min({m.size(rt) / (16 * sizeof(float)), c.size(rt) / (4 * sizeof(float)), g.size(rt) / (4 * sizeof(float))});
|
|
805
|
+
size_t n = count > 0 ? std::min(static_cast<size_t>(count), capacity) : 0;
|
|
806
|
+
return engine_render_publish(
|
|
807
|
+
static_cast<int32_t>(surface), reinterpret_cast<const double *>(f.data(rt)), f.size(rt) / sizeof(double),
|
|
808
|
+
reinterpret_cast<const float *>(m.data(rt)), reinterpret_cast<const float *>(c.data(rt)),
|
|
809
|
+
reinterpret_cast<const float *>(g.data(rt)), capacity, reinterpret_cast<const uint32_t *>(r.data(rt)),
|
|
810
|
+
r.size(rt) / sizeof(uint32_t), n, out.size(rt) >= 16 * sizeof(float) ? reinterpret_cast<float *>(out.data(rt)) : nullptr, find(world));
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
jsi::Array NayanEngineModule::renderStats(jsi::Runtime &rt, double surface) {
|
|
814
|
+
double out[2] = {0, 0};
|
|
815
|
+
engine_render_stats(static_cast<int32_t>(surface), out);
|
|
816
|
+
return jsi::Array::createWithElements(rt, out[0], out[1]);
|
|
817
|
+
}
|
|
818
|
+
|
|
658
819
|
void NayanEngineModule::renderOverlay(
|
|
659
820
|
jsi::Runtime &rt,
|
|
660
821
|
double surface,
|
|
@@ -713,6 +874,11 @@ double NayanEngineModule::audioLoad(jsi::Runtime &rt, jsi::Object data) {
|
|
|
713
874
|
return engine_audio_load_wav(buffer.data(rt), buffer.size(rt)); // copied by the core
|
|
714
875
|
}
|
|
715
876
|
|
|
877
|
+
jsi::Value NayanEngineModule::audioLoadAsync(jsi::Runtime &rt, jsi::Object data) {
|
|
878
|
+
auto bytes = std::make_shared<std::vector<uint8_t>>(copyBytes(rt, data, "audioLoadAsync"));
|
|
879
|
+
return runInBackground(rt, jsInvoker_, [bytes]() { return static_cast<double>(engine_audio_load_wav(bytes->data(), bytes->size())); });
|
|
880
|
+
}
|
|
881
|
+
|
|
716
882
|
double NayanEngineModule::audioPlay(jsi::Runtime &, double sound, double volume, double pan, double pitch, bool loop) {
|
|
717
883
|
return static_cast<double>(engine_audio_play(id(sound), volume, pan, pitch, loop ? 1 : 0));
|
|
718
884
|
}
|
|
@@ -800,6 +966,11 @@ Java_com_nayanui_engine_EngineSurfaceView_nativeAttach(JNIEnv *env, jclass, jint
|
|
|
800
966
|
extern "C" JNIEXPORT void JNICALL Java_com_nayanui_engine_EngineSurfaceView_nativeDetach(JNIEnv *, jclass, jint surface) {
|
|
801
967
|
releaseWindow(surface);
|
|
802
968
|
}
|
|
969
|
+
|
|
970
|
+
/** Called by EngineSurfaceView's Choreographer once per display refresh while it's attached and visible. */
|
|
971
|
+
extern "C" JNIEXPORT void JNICALL Java_com_nayanui_engine_EngineSurfaceView_nativeVsync(JNIEnv *, jclass, jint surface, jlong frameTimeNanos) {
|
|
972
|
+
facebook::react::deliverFrame(surface, static_cast<double>(frameTimeNanos) / 1e6);
|
|
973
|
+
}
|
|
803
974
|
extern "C" JNIEXPORT void JNICALL Java_com_nayanui_engine_EngineSurfaceView_nativeDrawingPresent(JNIEnv *, jclass, jint drawing, jint surface, jfloat width, jfloat height, jint color) {
|
|
804
975
|
engine_drawing_present(drawing, surface, width, height, static_cast<uint32_t>(color));
|
|
805
976
|
}
|
|
@@ -63,12 +63,14 @@ class NayanEngineModule : public NativeNayanEngineCxxSpec<NayanEngineModule> {
|
|
|
63
63
|
double shapePrepare(jsi::Runtime &rt, jsi::Object vertices, jsi::Object indices, jsi::String options);
|
|
64
64
|
double solidPrepare(jsi::Runtime &rt, jsi::String recipe, jsi::String options);
|
|
65
65
|
double modelLoad(jsi::Runtime &rt, jsi::Object data, bool center, double fit);
|
|
66
|
+
jsi::Value modelLoadAsync(jsi::Runtime &rt, jsi::Object data, bool center, double fit);
|
|
66
67
|
jsi::Object modelVertices(jsi::Runtime &rt, double mesh);
|
|
67
68
|
jsi::Object modelIndices(jsi::Runtime &rt, double mesh);
|
|
68
69
|
jsi::Array modelSize(jsi::Runtime &rt, double mesh);
|
|
69
70
|
double modelTexture(jsi::Runtime &rt, double mesh);
|
|
70
71
|
double meshAlias(jsi::Runtime &rt, double base);
|
|
71
72
|
double meshCreate(jsi::Runtime &rt, jsi::Object vertices, jsi::Object indices);
|
|
73
|
+
bool renderModel(jsi::Runtime &rt, double mesh, double texture, double shine);
|
|
72
74
|
jsi::Object pathExtrude(jsi::Runtime &rt, jsi::Object points, jsi::Array counts, double depth, bool evenOdd);
|
|
73
75
|
void geometrySmoothNormals(jsi::Runtime &rt, jsi::Object vertices, jsi::Object indices);
|
|
74
76
|
jsi::Object geometrySolid(jsi::Runtime &rt, jsi::String recipe);
|
|
@@ -85,9 +87,11 @@ class NayanEngineModule : public NativeNayanEngineCxxSpec<NayanEngineModule> {
|
|
|
85
87
|
jsi::Object outIndices,
|
|
86
88
|
double indexOffset);
|
|
87
89
|
double textureLoad(jsi::Runtime &rt, jsi::Object data);
|
|
90
|
+
jsi::Value textureLoadAsync(jsi::Runtime &rt, jsi::Object data);
|
|
88
91
|
jsi::Object texturePixels(jsi::Runtime &rt, double texture);
|
|
89
92
|
jsi::Array textureSize(jsi::Runtime &rt, double texture);
|
|
90
93
|
double fontLoad(jsi::Runtime &rt, jsi::Object data, double depth, jsi::String chars, double weight);
|
|
94
|
+
jsi::Value fontLoadAsync(jsi::Runtime &rt, jsi::Object data, double depth, jsi::String chars, double weight);
|
|
91
95
|
jsi::Array fontGlyphs(jsi::Runtime &rt, double font);
|
|
92
96
|
jsi::String loadError(jsi::Runtime &rt);
|
|
93
97
|
void generateBuiltinMeshes(jsi::Runtime &rt);
|
|
@@ -106,6 +110,23 @@ class NayanEngineModule : public NativeNayanEngineCxxSpec<NayanEngineModule> {
|
|
|
106
110
|
double count,
|
|
107
111
|
jsi::Object viewProj,
|
|
108
112
|
double world);
|
|
113
|
+
double publishFrame(
|
|
114
|
+
jsi::Runtime &rt,
|
|
115
|
+
double surface,
|
|
116
|
+
jsi::Object frame,
|
|
117
|
+
jsi::Object matrices,
|
|
118
|
+
jsi::Object colors,
|
|
119
|
+
jsi::Object regions,
|
|
120
|
+
jsi::Object ranges,
|
|
121
|
+
double count,
|
|
122
|
+
jsi::Object viewProj,
|
|
123
|
+
double world);
|
|
124
|
+
jsi::Array renderStats(jsi::Runtime &rt, double surface);
|
|
125
|
+
bool hasFrameClock(jsi::Runtime &rt);
|
|
126
|
+
void startFrames(jsi::Runtime &rt, double surface, jsi::Function onFrame);
|
|
127
|
+
void stopFrames(jsi::Runtime &rt, double surface);
|
|
128
|
+
/** Runs a surface's frame callback (on the JS thread); see `deliverFrame`. */
|
|
129
|
+
void callFrame(jsi::Runtime &rt, int surface, double timeMs);
|
|
109
130
|
void renderOverlay(
|
|
110
131
|
jsi::Runtime &rt,
|
|
111
132
|
double surface,
|
|
@@ -122,6 +143,7 @@ class NayanEngineModule : public NativeNayanEngineCxxSpec<NayanEngineModule> {
|
|
|
122
143
|
jsi::String drawingCommand(jsi::Runtime &rt, double document, jsi::String command);
|
|
123
144
|
void drawingDestroy(jsi::Runtime &rt, double document);
|
|
124
145
|
double audioLoad(jsi::Runtime &rt, jsi::Object data);
|
|
146
|
+
jsi::Value audioLoadAsync(jsi::Runtime &rt, jsi::Object data);
|
|
125
147
|
double audioPlay(jsi::Runtime &rt, double sound, double volume, double pan, double pitch, bool loop);
|
|
126
148
|
void audioStop(jsi::Runtime &rt, double voice);
|
|
127
149
|
void audioSetVolume(jsi::Runtime &rt, double volume);
|
|
@@ -139,6 +161,14 @@ class NayanEngineModule : public NativeNayanEngineCxxSpec<NayanEngineModule> {
|
|
|
139
161
|
std::unordered_map<int, std::shared_ptr<::World>> worlds_;
|
|
140
162
|
int next_ = 1;
|
|
141
163
|
std::unordered_set<int> drawings_;
|
|
164
|
+
/** Frame callbacks by surface; only touched on the JS thread. */
|
|
165
|
+
std::unordered_map<int, jsi::Function> frameCallbacks_;
|
|
142
166
|
};
|
|
143
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Called by the platform's display clock (any thread) once per refresh: schedules the surface's
|
|
170
|
+
* frame callback on the JS thread unless one is already waiting there.
|
|
171
|
+
*/
|
|
172
|
+
void deliverFrame(int surface, double timeMs);
|
|
173
|
+
|
|
144
174
|
} // namespace facebook::react
|