rngine 0.9.0 → 0.10.0

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.
@@ -1,4 +1,4 @@
1
- import type { GameEngineMethods, GameEngineProps } from './GameEngine.nitro';
1
+ import type { GameEngineMethods, GameEngineProps } from './GameEngine.nitro.js';
2
2
  /**
3
3
  * A game engine view component that renders the game world.
4
4
  * Size it using the style prop like any other React Native view.
@@ -1 +1 @@
1
- {"version":3,"file":"GameEngine.d.ts","sourceRoot":"","sources":["../../../src/GameEngine.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE7E;;;;;;;;GAQG;;AACH,wBAGE"}
1
+ {"version":3,"file":"GameEngine.d.ts","sourceRoot":"","sources":["../../../src/GameEngine.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,uBAAoB,CAAC;AAE7E;;;;;;;;GAQG;;AACH,wBAGE"}
@@ -1,5 +1,5 @@
1
- import type { Entity, EntityUpdate, ScreenUpdate } from './nativeTypes';
2
- import type { Config } from './types';
1
+ import type { Entity, EntityUpdate, ScreenUpdate } from './nativeTypes.js';
2
+ import type { Config } from './types.js';
3
3
  /** Sets up the game engine with the given configuration. Call this before anything else. */
4
4
  export declare const configure: ({ world, screen, entities, systems, paused, }: Config) => void;
5
5
  /** Pauses the game loop. Systems will stop running. */
@@ -1 +1 @@
1
- {"version":3,"file":"GameMethods.d.ts","sourceRoot":"","sources":["../../../src/GameMethods.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAU,MAAM,eAAe,CAAC;AAChF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGtC,4FAA4F;AAC5F,eAAO,MAAM,SAAS,GAAI,+CAMvB,MAAM,SAyBR,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,KAAK,YAA4B,CAAC;AAE/C,6BAA6B;AAC7B,eAAO,MAAM,MAAM,YAA6B,CAAC;AAEjD,0EAA0E;AAC1E,eAAO,MAAM,KAAK,GAAI,UAAU,MAAM,GAAG,MAAM,EAAE,SAGhD,CAAC;AAEF,2HAA2H;AAC3H,eAAO,MAAM,OAAO,GAAI,IAAI,MAAM,SAA4B,CAAC;AAE/D,+HAA+H;AAC/H,eAAO,MAAM,YAAY,GAAI,cAAc,YAAY,SAEtD,CAAC;AAEF,oGAAoG;AACpG,eAAO,MAAM,cAAc,GAAI,eAAe,YAAY,EAAE,SAE3D,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,YAAY,GAAI,cAAc,YAAY,SAEtD,CAAC;AA2CF;;yEAEyE;AACzE,eAAO,MAAM,UAAU,GAAU,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChE,QAAQ,CAAC,KACR,OAAO,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAAE,CASpC,CAAC"}
1
+ {"version":3,"file":"GameMethods.d.ts","sourceRoot":"","sources":["../../../src/GameMethods.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAU,MAAM,kBAAe,CAAC;AAChF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAS,CAAC;AAGtC,4FAA4F;AAC5F,eAAO,MAAM,SAAS,GAAI,+CAMvB,MAAM,SAyBR,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,KAAK,YAA4B,CAAC;AAE/C,6BAA6B;AAC7B,eAAO,MAAM,MAAM,YAA6B,CAAC;AAEjD,0EAA0E;AAC1E,eAAO,MAAM,KAAK,GAAI,UAAU,MAAM,GAAG,MAAM,EAAE,SAGhD,CAAC;AAEF,2HAA2H;AAC3H,eAAO,MAAM,OAAO,GAAI,IAAI,MAAM,SAA4B,CAAC;AAE/D,+HAA+H;AAC/H,eAAO,MAAM,YAAY,GAAI,cAAc,YAAY,SAEtD,CAAC;AAEF,oGAAoG;AACpG,eAAO,MAAM,cAAc,GAAI,eAAe,YAAY,EAAE,SAE3D,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,YAAY,GAAI,cAAc,YAAY,SAEtD,CAAC;AA2CF;;yEAEyE;AACzE,eAAO,MAAM,UAAU,GAAU,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChE,QAAQ,CAAC,KACR,OAAO,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM,GAAE,CASpC,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import type { HybridObject } from 'react-native-nitro-modules';
2
- import type { Entity, EntityUpdate, System, Screen, World, ScreenUpdate } from './nativeTypes';
2
+ import type { Entity, EntityUpdate, System, Screen, World, ScreenUpdate } from './nativeTypes.js';
3
3
  interface GameMethods extends HybridObject<{
4
4
  ios: 'c++';
5
5
  android: 'c++';
@@ -1 +1 @@
1
- {"version":3,"file":"GameMethods.nitro.d.ts","sourceRoot":"","sources":["../../../src/GameMethods.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,MAAM,EACN,MAAM,EACN,KAAK,EACL,YAAY,EACb,MAAM,eAAe,CAAC;AAGvB,UAAU,WAAY,SAAQ,YAAY,CAAC;IACzC,GAAG,EAAE,KAAK,CAAC;IACX,OAAO,EAAE,KAAK,CAAC;CAChB,CAAC;IACA,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEpC,2BAA2B;IAC3B,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IAEf,4BAA4B;IAC5B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAEpD,4BAA4B;IAC5B,YAAY,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAE/C,oBAAoB;IACpB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAED,eAAO,MAAM,WAAW,aACqC,CAAC"}
1
+ {"version":3,"file":"GameMethods.nitro.d.ts","sourceRoot":"","sources":["../../../src/GameMethods.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,MAAM,EACN,MAAM,EACN,KAAK,EACL,YAAY,EACb,MAAM,kBAAe,CAAC;AAGvB,UAAU,WAAY,SAAQ,YAAY,CAAC;IACzC,GAAG,EAAE,KAAK,CAAC;IACX,OAAO,EAAE,KAAK,CAAC;CAChB,CAAC;IACA,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAEpC,2BAA2B;IAC3B,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IAEf,4BAA4B;IAC5B,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,CAAC,aAAa,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAEpD,4BAA4B;IAC5B,YAAY,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAE/C,oBAAoB;IACpB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3D;AAED,eAAO,MAAM,WAAW,aACqC,CAAC"}
@@ -1,6 +1,6 @@
1
- import GameEngine from './GameEngine';
2
- export * from './GameMethods';
3
- export * from './types';
4
- export type { World, Screen, Entity, CollisionPair, Collision, EntityUpdate, SystemContext, } from './nativeTypes';
1
+ import GameEngine from './GameEngine.js';
2
+ export * from './GameMethods.js';
3
+ export * from './types.js';
4
+ export type { World, Screen, Entity, CollisionPair, Collision, EntityUpdate, SystemContext, } from './nativeTypes.js';
5
5
  export { GameEngine };
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,cAAc,CAAC;AAEtC,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,YAAY,EACV,KAAK,EACL,MAAM,EACN,MAAM,EACN,aAAa,EACb,SAAS,EACT,YAAY,EACZ,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,iBAAc,CAAC;AAEtC,cAAc,kBAAe,CAAC;AAC9B,cAAc,YAAS,CAAC;AACxB,YAAY,EACV,KAAK,EACL,MAAM,EACN,MAAM,EACN,aAAa,EACb,SAAS,EACT,YAAY,EACZ,aAAa,GACd,MAAM,kBAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { System as NativeSystem, Screen, Entity, World, SystemContext } from './nativeTypes';
1
+ import type { System as NativeSystem, Screen, Entity, World, SystemContext } from './nativeTypes.js';
2
2
  export type System = Omit<NativeSystem, 'onTick'> & {
3
3
  /** Called every tick with the resolved entities and collisions. */
4
4
  onTick: (systemContext: SystemContext) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,IAAI,YAAY,EACtB,MAAM,EACN,MAAM,EACN,KAAK,EACL,aAAa,EACd,MAAM,eAAe,CAAC;AAEvB,MAAM,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG;IAClD,mEAAmE;IACnE,MAAM,EAAE,CAAC,aAAa,EAAE,aAAa,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,6CAA6C;IAC7C,KAAK,EAAE,KAAK,CAAC;IACb,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,8GAA8G;IAC9G,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,wDAAwD;IACxD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,MAAM,IAAI,YAAY,EACtB,MAAM,EACN,MAAM,EACN,KAAK,EACL,aAAa,EACd,MAAM,kBAAe,CAAC;AAEvB,MAAM,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,GAAG;IAClD,mEAAmE;IACnE,MAAM,EAAE,CAAC,aAAa,EAAE,aAAa,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG;IACnB,6CAA6C;IAC7C,KAAK,EAAE,KAAK,CAAC;IACb,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,8GAA8G;IAC9G,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,wDAAwD;IACxD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC"}
@@ -15,38 +15,55 @@ namespace margelo::nitro::rngine::views {
15
15
  using namespace facebook;
16
16
  using ConcreteStateData = react::ConcreteState<HybridGameEngineState>;
17
17
 
18
- void JHybridGameEngineStateUpdater::updateViewProps(jni::alias_ref<jni::JClass> /* class */,
19
- jni::alias_ref<JHybridGameEngineSpec::JavaPart> javaView,
20
- jni::alias_ref<JStateWrapper::javaobject> stateWrapperInterface) {
21
- std::shared_ptr<JHybridGameEngineSpec> hybridView = javaView->getJHybridGameEngineSpec();
22
-
18
+ std::shared_ptr<const HybridGameEngineProps> JHybridGameEngineStateUpdater::getPropsFromStateWrapper(
19
+ jni::alias_ref<JStateWrapper::javaobject> stateWrapper) {
20
+ if (stateWrapper.get() == nullptr) {
21
+ return nullptr;
22
+ }
23
23
  // Get concrete StateWrapperImpl from passed StateWrapper interface object
24
- jobject rawStateWrapper = stateWrapperInterface.get();
25
- if (!stateWrapperInterface->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] {
26
- throw std::runtime_error("StateWrapper is not a StateWrapperImpl");
24
+ jobject rawStateWrapper = stateWrapper.get();
25
+ if (!stateWrapper->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] {
26
+ throw std::runtime_error("StateWrapper is not a StateWrapperImpl");
27
+ }
28
+ auto stateWrapperImpl = jni::alias_ref<react::StateWrapperImpl::javaobject>{
29
+ static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)
30
+ };
31
+ std::shared_ptr<const react::State> state = stateWrapperImpl->cthis()->getState();
32
+ if (state == nullptr) {
33
+ return nullptr;
27
34
  }
28
- auto stateWrapper = jni::alias_ref<react::StateWrapperImpl::javaobject>{
29
- static_cast<react::StateWrapperImpl::javaobject>(rawStateWrapper)};
30
- std::shared_ptr<const react::State> state = stateWrapper->cthis()->getState();
31
35
  auto concreteState = std::static_pointer_cast<const ConcreteStateData>(state);
32
36
  const HybridGameEngineState& data = concreteState->getData();
33
- const std::shared_ptr<HybridGameEngineProps>& props = data.getProps();
37
+ const std::shared_ptr<const HybridGameEngineProps>& props = data.getProps();
34
38
  if (props == nullptr) [[unlikely]] {
35
- // Props aren't set yet!
36
39
  throw std::runtime_error("HybridGameEngineState's data doesn't contain any props!");
37
40
  }
41
+ return props;
42
+ }
43
+
44
+ void JHybridGameEngineStateUpdater::updateViewProps(jni::alias_ref<jni::JClass> /* class */,
45
+ jni::alias_ref<JHybridGameEngineSpec::JavaPart> javaView,
46
+ jni::alias_ref<JStateWrapper::javaobject> newState,
47
+ jni::alias_ref<JStateWrapper::javaobject> oldState) {
48
+ std::shared_ptr<JHybridGameEngineSpec> hybridView = javaView->getJHybridGameEngineSpec();
49
+ std::shared_ptr<const HybridGameEngineProps> newProps = getPropsFromStateWrapper(newState);
50
+ std::shared_ptr<const HybridGameEngineProps> oldProps = getPropsFromStateWrapper(oldState);
51
+ if (newProps == nullptr) [[unlikely]] {
52
+ throw std::runtime_error("Current StateWrapper doesn't contain any props!");
53
+ }
38
54
 
39
- // Update all props if they are dirty
55
+ // Update only props that differ from the previous State snapshot.
40
56
 
41
57
 
42
58
  // Update hybridRef if it changed
43
- if (props->hybridRef.isDirty) {
59
+ if (oldProps == nullptr
60
+ ? newProps->hybridRef.isProvided()
61
+ : !newProps->hybridRef.hasSameValue(oldProps->hybridRef)) {
44
62
  // hybridRef changed - call it with new this
45
- const auto& maybeFunc = props->hybridRef.value;
63
+ const auto& maybeFunc = newProps->hybridRef.get();
46
64
  if (maybeFunc.has_value()) {
47
65
  maybeFunc.value()(hybridView);
48
66
  }
49
- props->hybridRef.isDirty = false;
50
67
  }
51
68
  }
52
69
 
@@ -31,7 +31,12 @@ public:
31
31
  public:
32
32
  static void updateViewProps(jni::alias_ref<jni::JClass> /* class */,
33
33
  jni::alias_ref<JHybridGameEngineSpec::JavaPart> view,
34
- jni::alias_ref<JStateWrapper::javaobject> stateWrapperInterface);
34
+ jni::alias_ref<JStateWrapper::javaobject> newState,
35
+ jni::alias_ref<JStateWrapper::javaobject> oldState);
36
+
37
+ private:
38
+ static std::shared_ptr<const HybridGameEngineProps> getPropsFromStateWrapper(
39
+ jni::alias_ref<JStateWrapper::javaobject> stateWrapper);
35
40
 
36
41
  public:
37
42
  static void registerNatives() {
@@ -10,6 +10,7 @@ package com.margelo.nitro.rngine
10
10
  import androidx.annotation.Keep
11
11
  import com.facebook.jni.HybridData
12
12
  import com.facebook.proguard.annotations.DoNotStrip
13
+ import dalvik.annotation.optimization.FastNative
13
14
  import com.margelo.nitro.core.HybridObject
14
15
  import com.margelo.nitro.views.HybridView
15
16
 
@@ -41,6 +42,7 @@ abstract class HybridGameEngineSpec: HybridView() {
41
42
  @Keep
42
43
  protected open class CxxPart(javaPart: HybridGameEngineSpec): HybridObject.CxxPart(javaPart) {
43
44
  // C++ JHybridGameEngineSpec::CxxPart::initHybrid(...)
45
+ @FastNative
44
46
  external override fun initHybrid(): HybridData
45
47
  }
46
48
  override fun createCxxPart(): CxxPart {
@@ -20,6 +20,14 @@ import com.margelo.nitro.rngine.*
20
20
  * Represents the React Native `ViewManager` for the "GameEngine" Nitro HybridView.
21
21
  */
22
22
  public class HybridGameEngineManager: SimpleViewManager<View>() {
23
+ /**
24
+ * Represents the View and its last state snapshot (mutable)
25
+ */
26
+ private class HybridViewHolder(
27
+ val hybridView: GameEngine,
28
+ var lastState: StateWrapper? = null,
29
+ )
30
+
23
31
  init {
24
32
  if (RecyclableView::class.java.isAssignableFrom(GameEngine::class.java)) {
25
33
  // Enable view recycling
@@ -34,33 +42,41 @@ public class HybridGameEngineManager: SimpleViewManager<View>() {
34
42
  override fun createViewInstance(reactContext: ThemedReactContext): View {
35
43
  val hybridView = GameEngine(reactContext)
36
44
  val view = hybridView.view
37
- view.setTag(associated_hybrid_view_tag, hybridView)
45
+ view.setTag(associated_hybrid_view_tag, HybridViewHolder(hybridView))
38
46
  return view
39
47
  }
40
48
 
41
49
  override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? {
42
- val hybridView = getHybridView(view)
50
+ val holder = getHybridViewHolder(view)
43
51
  ?: throw Error("Couldn't find view $view in local views table!")
52
+ val hybridView = holder.hybridView
53
+ val oldState = holder.lastState
54
+ val newState = stateWrapper
44
55
 
45
56
  // 1. Update each prop individually
46
57
  hybridView.beforeUpdate()
47
- HybridGameEngineStateUpdater.updateViewProps(hybridView, stateWrapper)
58
+ HybridGameEngineStateUpdater.updateViewProps(hybridView, newState, oldState)
48
59
  hybridView.afterUpdate()
60
+ holder.lastState = newState
49
61
 
50
62
  // 2. Continue in base View props
51
- return super.updateState(view, props, stateWrapper)
63
+ return super.updateState(view, props, newState)
52
64
  }
53
65
 
54
66
  override fun onDropViewInstance(view: View) {
55
- val hybridView = getHybridView(view)
56
- hybridView?.onDropView()
67
+ val holder = getHybridViewHolder(view)
68
+ holder?.lastState = null
69
+ holder?.hybridView?.onDropView()
57
70
  return super.onDropViewInstance(view)
58
71
  }
59
72
 
60
73
  protected override fun prepareToRecycleView(reactContext: ThemedReactContext, view: View): View? {
61
- super.prepareToRecycleView(reactContext, view)
62
- val hybridView = getHybridView(view)
74
+ val preparedView = super.prepareToRecycleView(reactContext, view)
75
+ ?: return null
76
+ val holder = getHybridViewHolder(preparedView)
63
77
  ?: return null
78
+ val hybridView = holder.hybridView
79
+ holder.lastState = null
64
80
 
65
81
  @Suppress("USELESS_IS_CHECK")
66
82
  if (hybridView is RecyclableView) {
@@ -74,7 +90,7 @@ public class HybridGameEngineManager: SimpleViewManager<View>() {
74
90
  }
75
91
  }
76
92
 
77
- private fun getHybridView(view: View): GameEngine? {
78
- return view.getTag(associated_hybrid_view_tag) as? GameEngine
93
+ private fun getHybridViewHolder(view: View): HybridViewHolder? {
94
+ return view.getTag(associated_hybrid_view_tag) as? HybridViewHolder
79
95
  }
80
96
  }
@@ -14,10 +14,10 @@ internal class HybridGameEngineStateUpdater {
14
14
  companion object {
15
15
  /**
16
16
  * Updates the props for [view] through C++.
17
- * The [state] prop is expected to contain [view]'s props as wrapped Fabric state.
17
+ * The [newState] prop is expected to contain [view]'s props as wrapped Fabric state.
18
18
  */
19
19
  @Suppress("KotlinJniMissingFunction")
20
20
  @JvmStatic
21
- external fun updateViewProps(view: HybridGameEngineSpec, state: StateWrapper)
21
+ external fun updateViewProps(view: HybridGameEngineSpec, newState: StateWrapper, oldState: StateWrapper?)
22
22
  }
23
23
  }
@@ -37,6 +37,7 @@ using namespace margelo::nitro::rngine::views;
37
37
 
38
38
  @implementation HybridGameEngineComponent {
39
39
  std::shared_ptr<HybridGameEngineSpecSwift> _hybridView;
40
+ BOOL _didDropView;
40
41
  }
41
42
 
42
43
  + (void) load {
@@ -50,6 +51,7 @@ using namespace margelo::nitro::rngine::views;
50
51
 
51
52
  - (instancetype) init {
52
53
  if (self = [super init]) {
54
+ _props = HybridGameEngineShadowNode::defaultSharedProps();
53
55
  std::shared_ptr<HybridGameEngineSpec> hybridView = Rngine::RngineAutolinking::createGameEngine();
54
56
  _hybridView = std::dynamic_pointer_cast<HybridGameEngineSpecSwift>(hybridView);
55
57
  [self updateView];
@@ -69,31 +71,50 @@ using namespace margelo::nitro::rngine::views;
69
71
  [self setContentView:view];
70
72
  }
71
73
 
74
+ - (void) notifyOnDropView {
75
+ // A recycled component can later be invalidated. Notify only once per mount.
76
+ if (_didDropView) {
77
+ return;
78
+ }
79
+ Rngine::HybridGameEngineSpec_cxx& swiftPart = _hybridView->getSwiftPart();
80
+ swiftPart.onDropView();
81
+ _didDropView = YES;
82
+ }
83
+
72
84
  - (void) updateProps:(const std::shared_ptr<const react::Props>&)props
73
85
  oldProps:(const std::shared_ptr<const react::Props>&)oldProps {
86
+ // A props update marks a newly mounted or still-active component.
87
+ _didDropView = NO;
88
+
74
89
  // 1. Downcast props
75
- const auto& newViewPropsConst = *std::static_pointer_cast<HybridGameEngineProps const>(props);
76
- auto& newViewProps = const_cast<HybridGameEngineProps&>(newViewPropsConst);
90
+ const auto& newViewProps = *std::static_pointer_cast<const HybridGameEngineProps>(props);
91
+ const auto* oldViewProps = static_cast<const HybridGameEngineProps*>(oldProps.get());
77
92
  Rngine::HybridGameEngineSpec_cxx& swiftPart = _hybridView->getSwiftPart();
78
93
 
79
- // 2. Update each prop individually
80
- swiftPart.beforeUpdate();
81
-
82
-
83
-
84
- swiftPart.afterUpdate();
85
-
86
- // 3. Update hybridRef if it changed
87
- if (newViewProps.hybridRef.isDirty) {
88
- // hybridRef changed - call it with new this
89
- const auto& maybeFunc = newViewProps.hybridRef.value;
90
- if (maybeFunc.has_value()) {
91
- maybeFunc.value()(_hybridView);
94
+ // 2. Update only props that differ from the previous Props snapshot.
95
+ const bool hasTransactionPropChanges = oldViewProps == nullptr
96
+ ? newViewProps.hasAnyProvidedProps()
97
+ : !newViewProps.hasSameProps(*oldViewProps);
98
+ if (hasTransactionPropChanges) {
99
+ swiftPart.beforeUpdate();
100
+
101
+
102
+
103
+ // Update hybridRef if it changed
104
+ if (oldViewProps == nullptr
105
+ ? newViewProps.hybridRef.isProvided()
106
+ : !newViewProps.hybridRef.hasSameValue(oldViewProps->hybridRef)) {
107
+ // hybridRef changed - call it with new this
108
+ const auto& maybeFunc = newViewProps.hybridRef.get();
109
+ if (maybeFunc.has_value()) {
110
+ maybeFunc.value()(_hybridView);
111
+ }
92
112
  }
93
- newViewProps.hybridRef.isDirty = false;
113
+
114
+ swiftPart.afterUpdate();
94
115
  }
95
116
 
96
- // 4. Continue in base class
117
+ // 3. Continue in base class
97
118
  [super updateProps:props oldProps:oldProps];
98
119
  }
99
120
 
@@ -102,6 +123,7 @@ using namespace margelo::nitro::rngine::views;
102
123
  }
103
124
 
104
125
  - (void)prepareForRecycle {
126
+ [self notifyOnDropView];
105
127
  [super prepareForRecycle];
106
128
  Rngine::HybridGameEngineSpec_cxx& swiftPart = _hybridView->getSwiftPart();
107
129
  swiftPart.maybePrepareForRecycle();
@@ -109,8 +131,7 @@ using namespace margelo::nitro::rngine::views;
109
131
 
110
132
  #ifdef ENABLE_RCT_COMPONENT_VIEW_INVALIDATE
111
133
  - (void)invalidate {
112
- Rngine::HybridGameEngineSpec_cxx& swiftPart = _hybridView->getSwiftPart();
113
- swiftPart.onDropView();
134
+ [self notifyOnDropView];
114
135
  [super invalidate];
115
136
  }
116
137
  #endif
@@ -7,35 +7,20 @@
7
7
 
8
8
  #include "HybridGameEngineComponent.hpp"
9
9
 
10
- #include <string>
11
- #include <exception>
12
- #include <utility>
13
- #include <NitroModules/NitroDefines.hpp>
14
- #include <NitroModules/JSIConverter.hpp>
15
- #include <NitroModules/PropNameIDCache.hpp>
16
- #include <react/renderer/core/RawValue.h>
17
- #include <react/renderer/core/ShadowNode.h>
18
- #include <react/renderer/core/ComponentDescriptor.h>
19
- #include <react/renderer/components/view/ViewProps.h>
10
+ #include <NitroModules/NitroHash.hpp>
11
+ #include <NitroModules/ReactProp.hpp>
20
12
 
21
13
  namespace margelo::nitro::rngine::views {
22
14
 
15
+ using namespace facebook;
16
+
23
17
  extern const char HybridGameEngineComponentName[] = "GameEngine";
24
18
 
25
19
  HybridGameEngineProps::HybridGameEngineProps(const react::PropsParserContext& context,
26
20
  const HybridGameEngineProps& sourceProps,
27
21
  const react::RawProps& rawProps):
28
22
  react::ViewProps(context, sourceProps, rawProps, filterObjectKeys),
29
- hybridRef([&]() -> CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridGameEngineSpec>& /* ref */)>>> {
30
- try {
31
- const react::RawValue* rawValue = rawProps.at("hybridRef", nullptr, nullptr);
32
- if (rawValue == nullptr) return sourceProps.hybridRef;
33
- const auto& [runtime, value] = (std::pair<jsi::Runtime*, jsi::Value>)*rawValue;
34
- return CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridGameEngineSpec>& /* ref */)>>>::fromRawValue(*runtime, value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")), sourceProps.hybridRef);
35
- } catch (const std::exception& exc) {
36
- throw std::runtime_error(std::string("GameEngine.hybridRef: ") + exc.what());
37
- }
38
- }()) { }
23
+ hybridRef(nitro::ReactProp<std::optional<std::function<void(const std::shared_ptr<HybridGameEngineSpec>& /* ref */)>>>::fromRawValue("GameEngine", "hybridRef", rawProps, sourceProps.hybridRef)) { }
39
24
 
40
25
  bool HybridGameEngineProps::filterObjectKeys(const std::string& propName) {
41
26
  switch (hashString(propName)) {
@@ -44,29 +29,4 @@ namespace margelo::nitro::rngine::views {
44
29
  }
45
30
  }
46
31
 
47
- HybridGameEngineComponentDescriptor::HybridGameEngineComponentDescriptor(const react::ComponentDescriptorParameters& parameters)
48
- : ConcreteComponentDescriptor(parameters,
49
- react::RawPropsParser(/* enableJsiParser */ true)) {}
50
-
51
- std::shared_ptr<const react::Props> HybridGameEngineComponentDescriptor::cloneProps(const react::PropsParserContext& context,
52
- const std::shared_ptr<const react::Props>& props,
53
- react::RawProps rawProps) const {
54
- // 1. Prepare raw props parser
55
- rawProps.parse(rawPropsParser_);
56
- // 2. Copy props with Nitro's cached copy constructor
57
- return HybridGameEngineShadowNode::Props(context, /* & */ rawProps, props);
58
- }
59
-
60
- #ifdef ANDROID
61
- void HybridGameEngineComponentDescriptor::adopt(react::ShadowNode& shadowNode) const {
62
- // This is called immediately after `ShadowNode` is created, cloned or in progress.
63
- // On Android, we need to wrap props in our state, which gets routed through Java and later unwrapped in JNI/C++.
64
- auto& concreteShadowNode = static_cast<HybridGameEngineShadowNode&>(shadowNode);
65
- const std::shared_ptr<const HybridGameEngineProps>& constProps = concreteShadowNode.getConcreteSharedProps();
66
- const std::shared_ptr<HybridGameEngineProps>& props = std::const_pointer_cast<HybridGameEngineProps>(constProps);
67
- HybridGameEngineState state{props};
68
- concreteShadowNode.setStateData(std::move(state));
69
- }
70
- #endif
71
-
72
32
  } // namespace margelo::nitro::rngine::views
@@ -7,14 +7,15 @@
7
7
 
8
8
  #pragma once
9
9
 
10
- #include <optional>
11
- #include <NitroModules/NitroDefines.hpp>
12
- #include <NitroModules/NitroHash.hpp>
13
- #include <NitroModules/CachedProp.hpp>
14
- #include <react/renderer/core/ConcreteComponentDescriptor.h>
15
- #include <react/renderer/core/PropsParserContext.h>
10
+ #include <NitroModules/ReactProp.hpp>
11
+ #include <NitroModules/ViewComponentDescriptor.hpp>
12
+ #include <NitroModules/ViewPropsHolderState.hpp>
16
13
  #include <react/renderer/components/view/ConcreteViewShadowNode.h>
17
14
  #include <react/renderer/components/view/ViewProps.h>
15
+ #include <react/renderer/core/PropsParserContext.h>
16
+ #include <react/renderer/core/RawProps.h>
17
+
18
+ #include <string>
18
19
 
19
20
  #include <memory>
20
21
  #include "HybridGameEngineSpec.hpp"
@@ -41,7 +42,17 @@ namespace margelo::nitro::rngine::views {
41
42
  const react::RawProps& rawProps);
42
43
 
43
44
  public:
44
- CachedProp<std::optional<std::function<void(const std::shared_ptr<HybridGameEngineSpec>& /* ref */)>>> hybridRef;
45
+ nitro::ReactProp<std::optional<std::function<void(const std::shared_ptr<HybridGameEngineSpec>& /* ref */)>>> hybridRef;
46
+
47
+ [[nodiscard]]
48
+ bool hasSameProps(const HybridGameEngineProps& other) const noexcept {
49
+ return hybridRef.hasSameValue(other.hybridRef);
50
+ }
51
+
52
+ [[nodiscard]]
53
+ bool hasAnyProvidedProps() const noexcept {
54
+ return hybridRef.isProvided();
55
+ }
45
56
 
46
57
  private:
47
58
  static bool filterObjectKeys(const std::string& propName);
@@ -50,32 +61,7 @@ namespace margelo::nitro::rngine::views {
50
61
  /**
51
62
  * State for the "GameEngine" View.
52
63
  */
53
- class HybridGameEngineState final {
54
- public:
55
- HybridGameEngineState() = default;
56
- explicit HybridGameEngineState(const std::shared_ptr<HybridGameEngineProps>& props):
57
- _props(props) {}
58
-
59
- public:
60
- [[nodiscard]]
61
- const std::shared_ptr<HybridGameEngineProps>& getProps() const {
62
- return _props;
63
- }
64
-
65
- public:
66
- #ifdef ANDROID
67
- HybridGameEngineState(const HybridGameEngineState& /* previousState */, folly::dynamic /* data */) {}
68
- folly::dynamic getDynamic() const {
69
- throw std::runtime_error("HybridGameEngineState does not support folly!");
70
- }
71
- react::MapBuffer getMapBuffer() const {
72
- throw std::runtime_error("HybridGameEngineState does not support MapBuffer!");
73
- };
74
- #endif
75
-
76
- private:
77
- std::shared_ptr<HybridGameEngineProps> _props;
78
- };
64
+ using HybridGameEngineState = nitro::ViewPropsHolderState<HybridGameEngineProps>;
79
65
 
80
66
  /**
81
67
  * The Shadow Node for the "GameEngine" View.
@@ -88,21 +74,7 @@ namespace margelo::nitro::rngine::views {
88
74
  /**
89
75
  * The Component Descriptor for the "GameEngine" View.
90
76
  */
91
- class HybridGameEngineComponentDescriptor final: public react::ConcreteComponentDescriptor<HybridGameEngineShadowNode> {
92
- public:
93
- explicit HybridGameEngineComponentDescriptor(const react::ComponentDescriptorParameters& parameters);
94
-
95
- public:
96
- /**
97
- * A faster path for cloning props - reuses the caching logic from `HybridGameEngineProps`.
98
- */
99
- std::shared_ptr<const react::Props> cloneProps(const react::PropsParserContext& context,
100
- const std::shared_ptr<const react::Props>& props,
101
- react::RawProps rawProps) const override;
102
- #ifdef ANDROID
103
- void adopt(react::ShadowNode& shadowNode) const override;
104
- #endif
105
- };
77
+ using HybridGameEngineComponentDescriptor = nitro::ViewComponentDescriptor<HybridGameEngineShadowNode>;
106
78
 
107
79
  /* The actual view for "GameEngine" needs to be implemented in platform-specific code. */
108
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rngine",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "React Native 2D game engine powered by Nitro Modules",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -28,9 +28,6 @@
28
28
  "!android/gradlew",
29
29
  "!android/gradlew.bat",
30
30
  "!android/local.properties",
31
- "!**/__tests__",
32
- "!**/__fixtures__",
33
- "!**/__mocks__",
34
31
  "!**/.*"
35
32
  ],
36
33
  "scripts": {
@@ -39,11 +36,7 @@
39
36
  "prepare": "bob build && node scripts/nitrogen-patch.js",
40
37
  "nitrogen": "nitrogen",
41
38
  "typecheck": "tsc",
42
- "lint": "eslint \"**/*.{js,ts,tsx}\"",
43
- "test": "jest",
44
- "release": "release-it --only-version",
45
- "web": "vite",
46
- "build:web": "vite build"
39
+ "lint": "eslint \"**/*.{js,ts,tsx}\""
47
40
  },
48
41
  "keywords": [
49
42
  "react-native",
@@ -64,39 +57,33 @@
64
57
  "registry": "https://registry.npmjs.org/"
65
58
  },
66
59
  "devDependencies": {
67
- "@commitlint/config-conventional": "^20.5.0",
68
- "@eslint/compat": "^2.0.3",
69
- "@eslint/eslintrc": "^3.3.5",
60
+ "@commitlint/config-conventional": "^21.2.3",
61
+ "@eslint/compat": "^2.1.1",
62
+ "@eslint/eslintrc": "^3.3.7",
70
63
  "@eslint/js": "^10.0.1",
71
- "@jest/globals": "^30.0.0",
72
- "@react-native/babel-preset": "0.85.0",
73
- "@react-native/eslint-config": "0.85.0",
74
- "@react-native/jest-preset": "0.85.0",
75
- "@release-it/conventional-changelog": "^10.0.6",
76
- "@types/react": "^19.2.0",
77
- "commitlint": "^20.5.0",
64
+ "@react-native/babel-preset": "^0.87.1",
65
+ "@react-native/eslint-config": "^0.87.1",
66
+ "@types/react": "^19.3.0",
67
+ "commitlint": "^21.2.3",
78
68
  "del-cli": "^7.0.0",
79
- "eslint": "^9.39.4",
69
+ "eslint": "9.39.5",
80
70
  "eslint-config-prettier": "^10.1.8",
81
71
  "eslint-plugin-ft-flow": "^3.0.11",
82
- "eslint-plugin-prettier": "^5.5.5",
83
- "jest": "^30.3.0",
84
- "lefthook": "^2.1.4",
85
- "nitrogen": "^0.35.3",
86
- "prettier": "^3.8.1",
72
+ "eslint-plugin-prettier": "^5.5.6",
73
+ "lefthook": "^2.1.14",
74
+ "nitrogen": "^0.37.1",
75
+ "prettier": "^3.9.8",
87
76
  "react": "19.2.3",
88
- "react-native": "0.85.0",
89
- "react-native-builder-bob": "^0.41.0",
90
- "react-native-nitro-modules": "^0.35.3",
91
- "react-native-web": "~0.21.1",
92
- "release-it": "^19.2.4",
93
- "turbo": "^2.8.21",
77
+ "react-native": "0.85.3",
78
+ "react-native-builder-bob": "^0.43.1",
79
+ "react-native-nitro-modules": "^0.37.1",
80
+ "turbo": "^2.11.2",
94
81
  "typescript": "^6.0.2"
95
82
  },
96
83
  "peerDependencies": {
97
84
  "react": "*",
98
85
  "react-native": "*",
99
- "react-native-nitro-modules": "^0.35.3"
86
+ "react-native-nitro-modules": "^0.37.1"
100
87
  },
101
88
  "workspaces": [
102
89
  "example"
@@ -134,50 +121,21 @@
134
121
  "trailingComma": "es5",
135
122
  "useTabs": false
136
123
  },
137
- "jest": {
138
- "preset": "@react-native/jest-preset",
139
- "modulePathIgnorePatterns": [
140
- "<rootDir>/example/node_modules",
141
- "<rootDir>/lib/"
142
- ]
143
- },
144
124
  "commitlint": {
145
125
  "extends": [
146
126
  "@commitlint/config-conventional"
147
127
  ]
148
128
  },
149
- "release-it": {
150
- "git": {
151
- "commitMessage": "chore: release ${version}",
152
- "tagName": "v${version}"
153
- },
154
- "npm": {
155
- "publish": true
156
- },
157
- "github": {
158
- "release": true
159
- },
160
- "plugins": {
161
- "@release-it/conventional-changelog": {
162
- "preset": {
163
- "name": "angular"
164
- }
165
- }
166
- }
167
- },
168
129
  "create-react-native-library": {
169
130
  "type": "nitro-view",
170
131
  "languages": "kotlin-swift",
171
132
  "tools": [
172
133
  "eslint",
173
- "jest",
174
- "lefthook",
175
- "release-it",
176
- "vite"
134
+ "lefthook"
177
135
  ],
178
136
  "version": "0.61.0"
179
137
  },
180
138
  "dependencies": {
181
- "react-native-skia-android": "^152.0.0"
139
+ "react-native-skia-android": "^154.0.0"
182
140
  }
183
141
  }
@@ -71,6 +71,8 @@ void GameLoop::runGameLoop() {
71
71
  auto &gameRenderer = GameRenderer::getInstance();
72
72
 
73
73
  while (_isRunning) {
74
+ gameRenderer.processPendingTasks();
75
+
74
76
  double targetDeltaTime;
75
77
  {
76
78
  std::lock_guard<std::mutex> lock(_worldMutex);
@@ -43,63 +43,85 @@ GameRenderer::~GameRenderer() {
43
43
  }
44
44
 
45
45
  void GameRenderer::onSurfaceCreated(ANativeWindow *window) {
46
- __android_log_print(ANDROID_LOG_INFO, "GameRenderer", "onSurfaceCreated");
47
46
 
48
- _nativeWindow = window;
47
+ std::lock_guard<std::mutex> lock(_taskMutex);
48
+ _taskQueue.push([this, window]() {
49
+ __android_log_print(ANDROID_LOG_INFO, "GameRenderer", "onSurfaceCreated");
49
50
 
50
- if (!_initialized) {
51
- if (!initializeEGL()) {
52
- __android_log_print(ANDROID_LOG_ERROR, "GameRenderer",
53
- "onSurfaceCreated: EGL initialization failed");
54
- return;
51
+ _nativeWindow = window;
52
+
53
+ if (!_initialized) {
54
+ if (!initializeEGL()) {
55
+ __android_log_print(ANDROID_LOG_ERROR, "GameRenderer",
56
+ "onSurfaceCreated: EGL initialization failed");
57
+ return;
58
+ }
59
+ _initialized = true;
55
60
  }
56
- _initialized = true;
57
- }
58
61
 
59
- if (!createEGLSurface()) {
60
- __android_log_print(ANDROID_LOG_ERROR, "GameRenderer",
61
- "onSurfaceCreated: EGL surface creation failed");
62
- }
62
+ if (!createEGLSurface()) {
63
+ __android_log_print(ANDROID_LOG_ERROR, "GameRenderer",
64
+ "onSurfaceCreated: EGL surface creation failed");
65
+ }
66
+ });
63
67
  }
64
68
 
65
69
  void GameRenderer::onSurfaceChanged(int width, int height) {
66
- _width = width;
67
- _height = height;
68
- _surface = nullptr;
69
-
70
- __android_log_print(ANDROID_LOG_INFO, "GameRenderer",
71
- "onSurfaceChanged: Width: %d, Height: %d", _width,
72
- _height);
70
+ std::lock_guard<std::mutex> lock(_taskMutex);
71
+ _taskQueue.push([this, width, height]() {
72
+ _width = width;
73
+ _height = height;
74
+ _surface = nullptr;
75
+
76
+ __android_log_print(ANDROID_LOG_INFO, "GameRenderer",
77
+ "onSurfaceChanged: Width: %d, Height: %d", _width,
78
+ _height);
79
+ });
73
80
  }
74
81
 
75
82
  void GameRenderer::onSurfaceDestroyed() {
76
- if (_eglDisplay != EGL_NO_DISPLAY) {
77
- eglMakeCurrent(_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
83
+ std::lock_guard<std::mutex> lock(_taskMutex);
84
+ _taskQueue.push([this]() {
85
+ if (_eglDisplay != EGL_NO_DISPLAY) {
86
+ eglMakeCurrent(_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
87
+ EGL_NO_CONTEXT);
88
+
89
+ if (_eglSurface != EGL_NO_SURFACE) {
90
+ eglDestroySurface(_eglDisplay, _eglSurface);
91
+ _eglSurface = EGL_NO_SURFACE;
92
+ }
93
+ }
78
94
 
79
- if (_eglSurface != EGL_NO_SURFACE) {
80
- eglDestroySurface(_eglDisplay, _eglSurface);
81
- _eglSurface = EGL_NO_SURFACE;
95
+ if (_nativeWindow) {
96
+ ANativeWindow_release(_nativeWindow);
97
+ _nativeWindow = nullptr;
82
98
  }
83
- }
84
99
 
85
- if (_nativeWindow) {
86
- ANativeWindow_release(_nativeWindow);
87
- _nativeWindow = nullptr;
88
- }
100
+ _surface = nullptr;
101
+ _width = 0;
102
+ _height = 0;
103
+
104
+ __android_log_print(ANDROID_LOG_INFO, "GameRenderer", "onSurfaceDestroyed");
105
+ });
106
+ }
89
107
 
90
- _surface = nullptr;
91
- _width = 0;
92
- _height = 0;
108
+ void GameRenderer::processPendingTasks() {
109
+ std::queue<std::function<void()>> tasksToProcess;
93
110
 
94
- __android_log_print(ANDROID_LOG_INFO, "GameRenderer", "onSurfaceDestroyed");
111
+ {
112
+ std::lock_guard<std::mutex> lock(_taskMutex);
113
+ std::swap(tasksToProcess, _taskQueue);
114
+ }
115
+
116
+ while (!tasksToProcess.empty()) {
117
+ tasksToProcess.front()(); // Executes on the game loop thread
118
+ tasksToProcess.pop();
119
+ }
95
120
  }
96
121
 
97
122
  void GameRenderer::render(const Screen &screen,
98
123
  const std::map<std::string, Entity> &entities) {
99
124
 
100
- __android_log_print(ANDROID_LOG_DEBUG, "GameRenderer",
101
- "render: called, entities=%zu", entities.size());
102
-
103
125
  if (!_grContext || _eglSurface == EGL_NO_SURFACE || _width == 0 ||
104
126
  _height == 0) {
105
127
  __android_log_print(ANDROID_LOG_WARN, "GameRenderer",
@@ -107,12 +129,6 @@ void GameRenderer::render(const Screen &screen,
107
129
  return;
108
130
  }
109
131
 
110
- if (!eglMakeCurrent(_eglDisplay, _eglSurface, _eglSurface, _eglContext)) {
111
- __android_log_print(ANDROID_LOG_ERROR, "GameRenderer",
112
- "render: eglMakeCurrent failed: 0x%x", eglGetError());
113
- return;
114
- }
115
-
116
132
  if (!_surface) {
117
133
  if (!createSkSurface()) {
118
134
  return;
@@ -228,8 +244,6 @@ void GameRenderer::render(const Screen &screen,
228
244
 
229
245
  _grContext->flushAndSubmit(_surface.get());
230
246
  eglSwapBuffers(_eglDisplay, _eglSurface);
231
-
232
- __android_log_print(ANDROID_LOG_DEBUG, "GameRenderer", "render: complete");
233
247
  }
234
248
 
235
249
  bool GameRenderer::isEntityVisible(const Entity &entity,
@@ -342,8 +356,6 @@ bool GameRenderer::createEGLSurface() {
342
356
  }
343
357
  }
344
358
 
345
- eglMakeCurrent(_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
346
-
347
359
  __android_log_print(ANDROID_LOG_INFO, "GameRenderer",
348
360
  "createEGLSurface: EGL surface created");
349
361
  return true;
@@ -36,6 +36,7 @@ public:
36
36
  void onSurfaceChanged(int width, int height);
37
37
  void onSurfaceDestroyed();
38
38
 
39
+ void processPendingTasks();
39
40
  void render(const Screen &screen,
40
41
  const std::map<std::string, Entity> &entities);
41
42
 
@@ -59,6 +60,9 @@ private:
59
60
  std::map<double, sk_sp<skottie::Animation>> _lottieCache;
60
61
  std::map<double, std::variant<sk_sp<SkSVGDOM>, sk_sp<SkImage>>> _imageCache;
61
62
 
63
+ std::mutex _taskMutex;
64
+ std::queue<std::function<void()>> _taskQueue;
65
+
62
66
  bool isEntityVisible(const Entity &entity, const SkRect &viewBounds);
63
67
 
64
68
  bool initializeEGL();
Binary file