@onekeyfe/react-native-perf-stats 3.0.35 → 3.0.37

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.
Files changed (35) hide show
  1. package/android/src/main/java/com/margelo/nitro/reactnativeperfstats/PerfStatsInitProvider.kt +11 -4
  2. package/android/src/main/java/com/margelo/nitro/reactnativeperfstats/ReactNativePerfStats.kt +333 -24
  3. package/ios/ReactNativePerfStats.swift +360 -15
  4. package/lib/module/index.js +77 -5
  5. package/lib/module/index.js.map +1 -1
  6. package/lib/typescript/src/ReactNativePerfStats.nitro.d.ts +109 -1
  7. package/lib/typescript/src/ReactNativePerfStats.nitro.d.ts.map +1 -1
  8. package/lib/typescript/src/index.d.ts +31 -0
  9. package/lib/typescript/src/index.d.ts.map +1 -1
  10. package/nitrogen/generated/android/c++/JFunc_void_MemoryWarningEvent.hpp +79 -0
  11. package/nitrogen/generated/android/c++/JHybridReactNativePerfStatsSpec.cpp +24 -0
  12. package/nitrogen/generated/android/c++/JHybridReactNativePerfStatsSpec.hpp +3 -0
  13. package/nitrogen/generated/android/c++/JMemoryWarningEvent.hpp +66 -0
  14. package/nitrogen/generated/android/c++/JMemoryWarningLevel.hpp +59 -0
  15. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeperfstats/Func_void_MemoryWarningEvent.kt +80 -0
  16. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeperfstats/HybridReactNativePerfStatsSpec.kt +17 -0
  17. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeperfstats/MemoryWarningEvent.kt +44 -0
  18. package/nitrogen/generated/android/kotlin/com/margelo/nitro/reactnativeperfstats/MemoryWarningLevel.kt +21 -0
  19. package/nitrogen/generated/android/reactnativeperfstatsOnLoad.cpp +2 -0
  20. package/nitrogen/generated/ios/ReactNativePerfStats-Swift-Cxx-Bridge.cpp +8 -0
  21. package/nitrogen/generated/ios/ReactNativePerfStats-Swift-Cxx-Bridge.hpp +37 -0
  22. package/nitrogen/generated/ios/ReactNativePerfStats-Swift-Cxx-Umbrella.hpp +7 -0
  23. package/nitrogen/generated/ios/c++/HybridReactNativePerfStatsSpecSwift.hpp +27 -0
  24. package/nitrogen/generated/ios/swift/Func_void_MemoryWarningEvent.swift +47 -0
  25. package/nitrogen/generated/ios/swift/HybridReactNativePerfStatsSpec.swift +3 -0
  26. package/nitrogen/generated/ios/swift/HybridReactNativePerfStatsSpec_cxx.swift +39 -0
  27. package/nitrogen/generated/ios/swift/MemoryWarningEvent.swift +58 -0
  28. package/nitrogen/generated/ios/swift/MemoryWarningLevel.swift +40 -0
  29. package/nitrogen/generated/shared/c++/HybridReactNativePerfStatsSpec.cpp +3 -0
  30. package/nitrogen/generated/shared/c++/HybridReactNativePerfStatsSpec.hpp +7 -0
  31. package/nitrogen/generated/shared/c++/MemoryWarningEvent.hpp +84 -0
  32. package/nitrogen/generated/shared/c++/MemoryWarningLevel.hpp +76 -0
  33. package/package.json +1 -1
  34. package/src/ReactNativePerfStats.nitro.ts +114 -1
  35. package/src/index.tsx +90 -5
package/src/index.tsx CHANGED
@@ -1,5 +1,8 @@
1
1
  import { NitroModules } from 'react-native-nitro-modules';
2
- import type { ReactNativePerfStats as ReactNativePerfStatsType } from './ReactNativePerfStats.nitro';
2
+ import type {
3
+ MemoryWarningEvent,
4
+ ReactNativePerfStats as ReactNativePerfStatsType,
5
+ } from './ReactNativePerfStats.nitro';
3
6
 
4
7
  const nativeImpl =
5
8
  NitroModules.createHybridObject<ReactNativePerfStatsType>('ReactNativePerfStats');
@@ -28,6 +31,11 @@ let jsFpsIntervalId: ReturnType<typeof setInterval> | null = null;
28
31
  let jsFpsFrameCount = 0;
29
32
  let jsFpsCurrentInterval: number | null = null;
30
33
 
34
+ // Module-level latch so forceGarbageCollection's "no GC binding" branch
35
+ // warns exactly once per JS realm. Avoids spamming the console when a
36
+ // memory-warning handler calls forceGarbageCollection on every event.
37
+ let forceGcMissingWarned = false;
38
+
31
39
  /**
32
40
  * Start the JS-side FPS ticker. Normally invoked automatically by
33
41
  * `ReactNativePerfStats.start` and stopped by `.stop`; exported as
@@ -39,9 +47,17 @@ let jsFpsCurrentInterval: number | null = null;
39
47
  * interval is a no-op.
40
48
  */
41
49
  export function startJsFpsTracker(reportIntervalMs: number = 1000): void {
42
- if (jsFpsCurrentInterval === reportIntervalMs) return;
50
+ // Clamp before use: `setInterval(_, 0)` fires every event-loop tick and
51
+ // the per-second divide below would race away to Infinity; NaN/negative
52
+ // values are equally meaningless here. 100 ms is below one rAF frame
53
+ // on a 60 Hz display, so it's already finer than the data warrants;
54
+ // 60 s is an arbitrary upper sanity bound.
55
+ const safeInterval = Number.isFinite(reportIntervalMs)
56
+ ? Math.max(100, Math.min(60_000, Math.trunc(reportIntervalMs)))
57
+ : 1000;
58
+ if (jsFpsCurrentInterval === safeInterval) return;
43
59
  stopJsFpsTracker();
44
- jsFpsCurrentInterval = reportIntervalMs;
60
+ jsFpsCurrentInterval = safeInterval;
45
61
 
46
62
  const tick = () => {
47
63
  jsFpsFrameCount += 1;
@@ -50,10 +66,10 @@ export function startJsFpsTracker(reportIntervalMs: number = 1000): void {
50
66
  jsFpsRafId = requestAnimationFrame(tick);
51
67
 
52
68
  jsFpsIntervalId = setInterval(() => {
53
- const fps = (jsFpsFrameCount * 1000) / reportIntervalMs;
69
+ const fps = (jsFpsFrameCount * 1000) / safeInterval;
54
70
  jsFpsFrameCount = 0;
55
71
  nativeImpl.setJsFpsHint(fps);
56
- }, reportIntervalMs);
72
+ }, safeInterval);
57
73
  }
58
74
 
59
75
  /** Stop the JS-side FPS ticker. Idempotent. */
@@ -89,4 +105,73 @@ export const ReactNativePerfStats = {
89
105
  hideOverlay: (): void => nativeImpl.hideOverlay(),
90
106
  sample: () => nativeImpl.sample(),
91
107
  setJsFpsHint: (fps: number): void => nativeImpl.setJsFpsHint(fps),
108
+ // Memory pressure is independent of the sampler — these stay active
109
+ // even after `stop()`. iOS only emits `level: 'critical'`.
110
+ addMemoryWarningListener: (
111
+ callback: (event: MemoryWarningEvent) => void,
112
+ ): number => nativeImpl.addMemoryWarningListener(callback),
113
+ removeMemoryWarningListener: (id: number): void =>
114
+ nativeImpl.removeMemoryWarningListener(id),
115
+ /**
116
+ * Run the same native reclaim path the OS memory-warning observer
117
+ * triggers, on demand. Returns immediately (heavy work runs async
118
+ * on iOS). See the spec doc for what gets dropped on each platform.
119
+ */
120
+ cleanupNativeCaches: (): void => nativeImpl.cleanupNativeCaches(),
121
+
122
+ /**
123
+ * Best-effort hint to the JS engine that now is a good time to GC.
124
+ *
125
+ * Hermes does not expose a public `collectGarbage` binding in production
126
+ * builds; the only stable JS-level entry point is the (undocumented)
127
+ * `HermesInternal.gc` property, which is present in some builds and
128
+ * absent in others. We feature-detect it and fall back to a no-op so
129
+ * callers never have to branch.
130
+ *
131
+ * Returns `true` only if a GC binding was both found AND invoked
132
+ * without throwing. A `false` return therefore covers three cases —
133
+ * binding missing (production Hermes is the common case), binding
134
+ * present but threw, and any unexpected failure. The first miss is
135
+ * logged once via `console.warn` so the caller knows it landed in the
136
+ * "no binding" branch; throws are logged on every occurrence because
137
+ * those are real errors.
138
+ *
139
+ * Cost: when honoured, Hermes does a stop-the-world collection that
140
+ * can take 100–500 ms — never call this on the hot path. Memory-warning
141
+ * handlers are the intended use case.
142
+ */
143
+ forceGarbageCollection(): boolean {
144
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
145
+ const g = globalThis as any;
146
+ const hi = g?.HermesInternal;
147
+ if (hi && typeof hi.gc === 'function') {
148
+ try {
149
+ hi.gc();
150
+ return true;
151
+ } catch (e) {
152
+ console.warn('[PerfStats] HermesInternal.gc threw:', e);
153
+ return false;
154
+ }
155
+ }
156
+ if (typeof g?.gc === 'function') {
157
+ // V8-style binding (only present with --expose-gc; never in
158
+ // production Hermes, but harmless to try).
159
+ try {
160
+ g.gc();
161
+ return true;
162
+ } catch (e) {
163
+ console.warn('[PerfStats] globalThis.gc threw:', e);
164
+ return false;
165
+ }
166
+ }
167
+ if (!forceGcMissingWarned) {
168
+ forceGcMissingWarned = true;
169
+ console.warn(
170
+ '[PerfStats] forceGarbageCollection: no GC binding found ' +
171
+ '(HermesInternal.gc / globalThis.gc absent). Production Hermes ' +
172
+ 'strips these; this warning fires once per JS realm.',
173
+ );
174
+ }
175
+ return false;
176
+ },
92
177
  };