Skip to content

Commit

Permalink
Add one listener per sensor config (#4340)
Browse files Browse the repository at this point in the history
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

## Summary

This PR is to optimise using sensors.

Before:

Each `useAnimatedSensor` call creates a new native listener that update
shared value assigned to it.

Now:

We identify sensors listeners by sensor type and config properties and
the same configurations use the same shared value.
 

## Test plan

- Example app
```
import Animated, {
  useAnimatedStyle,
  useAnimatedSensor,
  SensorType,
} from 'react-native-reanimated';
import { StyleSheet, View } from 'react-native';

import React from 'react';

export default function AnimatedSensorExample() {

  const sensorObject = (color: string, sensorType: any) => {
    const gravity = useAnimatedSensor(sensorType, { interval: 16 });
    console.log(gravity.sensor.value);

    const animatedStyle = useAnimatedStyle(() => {
      //console.log(gravity.sensor.value);
      return {
        top: -gravity.sensor.value.y * 300,
        left: gravity.sensor.value.x * 200,
      };
    });


    return (
      <View style={styles.container}>
        <Animated.View style={[{...styles.box, backgroundColor: color}, animatedStyle]} />
      </View>
    )
  };

  return (
    <View style={styles.container}>
      {sensorObject('red', SensorType.GRAVITY)}
      {sensorObject('blue', SensorType.ACCELEROMETER)}
      {sensorObject('green', SensorType.GRAVITY)}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  box: {
    width: 150,
    height: 150,
    backgroundColor: 'navy',
  },
});

```
  • Loading branch information
mstach60161 committed Apr 24, 2023
1 parent f9ef042 commit eafb345
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 77 deletions.
11 changes: 8 additions & 3 deletions __tests__/sensors.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { SensorType, useAnimatedSensor, Value3D, ValueRotation } from '../src/';
import {
SensorConfig,
SensorType,
useAnimatedSensor,
Value3D,
ValueRotation,
} from '../src/';

let eventHandler: (data: Value3D | ValueRotation) => void;

Expand All @@ -11,8 +17,7 @@ jest.mock('../src/reanimated2/core', () => {
...originalModule,
registerSensor: (
sensorType: number,
interval: number,
iosReferenceFrame: number,
config: SensorConfig,
_eventHandler: (data: Value3D | ValueRotation) => void
) => {
eventHandler = _eventHandler;
Expand Down
81 changes: 81 additions & 0 deletions src/reanimated2/Sensor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import NativeReanimatedModule from './NativeReanimated';
import {
SensorType,
SensorConfig,
SharedValue,
Value3D,
ValueRotation,
ShareableRef,
} from './commonTypes';
import { makeMutable } from './mutables';

function initSensorData(
sensorType: SensorType
): SharedValue<Value3D | ValueRotation> {
if (sensorType === SensorType.ROTATION) {
return makeMutable<Value3D | ValueRotation>({
qw: 0,
qx: 0,
qy: 0,
qz: 0,
yaw: 0,
pitch: 0,
roll: 0,
interfaceOrientation: 0,
});
} else {
return makeMutable<Value3D | ValueRotation>({
x: 0,
y: 0,
z: 0,
interfaceOrientation: 0,
});
}
}

export default class Sensor<T> {
public listenersNumber = 0;
private sensorId: number | null = null;
private sensorType: SensorType;
private data: SharedValue<Value3D | ValueRotation>;
private config: SensorConfig;

constructor(sensorType: SensorType, config: SensorConfig) {
this.sensorType = sensorType;
this.config = config;
this.data = initSensorData(sensorType);
}

register(
eventHandler: ShareableRef<T> | ((data: Value3D | ValueRotation) => void)
) {
const config = this.config;
const sensorType = this.sensorType;
this.sensorId = NativeReanimatedModule.registerSensor(
sensorType,
config.interval === 'auto' ? -1 : config.interval,
config.iosReferenceFrame,
eventHandler
);
return this.sensorId !== -1;
}

isRunning() {
return this.sensorId !== -1 && this.sensorId !== null;
}

isAvailable() {
return this.sensorId !== -1;
}

getSharedValue() {
return this.data;
}

unregister() {
if (this.sensorId !== null && this.sensorId !== -1) {
NativeReanimatedModule.unregisterSensor(this.sensorId);
}
this.sensorId = null;
}
}
71 changes: 71 additions & 0 deletions src/reanimated2/SensorContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
SensorType,
SensorConfig,
Value3D,
ValueRotation,
ShareableRef,
SharedValue,
} from './commonTypes';
import Sensor from './Sensor';

export class SensorContainer {
private nativeSensors: Map<number, Sensor<any>> = new Map();

getSensorId(sensorType: SensorType, config: SensorConfig) {
return (
sensorType * 100 +
config.iosReferenceFrame * 10 +
Number(config.adjustToInterfaceOrientation)
);
}

initializeSensor(
sensorType: SensorType,
config: SensorConfig
): SharedValue<Value3D | ValueRotation> {
const sensorId = this.getSensorId(sensorType, config);

if (!this.nativeSensors.has(sensorId)) {
const newSensor = new Sensor(sensorType, config);
this.nativeSensors.set(sensorId, newSensor);
}

const sensor = this.nativeSensors.get(sensorId);
return sensor!.getSharedValue();
}

registerSensor<T>(
sensorType: SensorType,
config: SensorConfig,
handler: ShareableRef<T> | ((data: Value3D | ValueRotation) => void)
): number {
const sensorId = this.getSensorId(sensorType, config);

if (!this.nativeSensors.has(sensorId)) {
return -1;
}

const sensor = this.nativeSensors.get(sensorId);
if (
sensor &&
sensor.isAvailable() &&
(sensor.isRunning() || sensor.register(handler))
) {
sensor.listenersNumber++;
return sensorId;
}
return -1;
}

unregisterSensor(sensorId: number) {
if (this.nativeSensors.has(sensorId)) {
const sensor = this.nativeSensors.get(sensorId);
if (sensor && sensor.isRunning()) {
sensor.listenersNumber--;
if (sensor.listenersNumber === 0) {
sensor.unregister();
}
}
}
}
}
13 changes: 13 additions & 0 deletions src/reanimated2/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,19 @@ export enum IOSReferenceFrame {
Auto,
}

export type SensorConfig = {
interval: number | 'auto';
adjustToInterfaceOrientation: boolean;
iosReferenceFrame: IOSReferenceFrame;
};

export type AnimatedSensor = {
sensor: SharedValue<Value3D | ValueRotation>;
unregister: () => void;
isAvailable: boolean;
config: SensorConfig;
};

export interface NumericAnimation {
current?: number;
}
Expand Down
35 changes: 27 additions & 8 deletions src/reanimated2/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { nativeShouldBeMock, shouldBeUseWeb, isWeb } from './PlatformChecker';
import {
AnimatedKeyboardOptions,
BasicWorkletFunction,
SensorConfig,
SensorType,
SharedValue,
Value3D,
ValueRotation,
} from './commonTypes';
Expand All @@ -20,6 +23,7 @@ import {
LayoutAnimationType,
} from './layoutReanimation';
import { initializeUIRuntime } from './initializers';
import { SensorContainer } from './SensorContainer';

export { stopMapper } from './mappers';
export { runOnJS, runOnUI } from './threads';
Expand Down Expand Up @@ -113,6 +117,13 @@ export function getViewProp<T>(viewTag: string, propName: string): Promise<T> {
});
}

export function getSensorContainer(): SensorContainer {
if (!global.__sensorContainer) {
global.__sensorContainer = new SensorContainer();
}
return global.__sensorContainer;
}

export function registerEventHandler<T>(
eventHash: string,
eventHandler: (event: T) => void
Expand Down Expand Up @@ -159,24 +170,32 @@ export function unsubscribeFromKeyboardEvents(listenerId: number): void {
}

export function registerSensor(
sensorType: number,
interval: number,
iosReferenceFrame: number,
sensorType: SensorType,
config: SensorConfig,
eventHandler: (
data: Value3D | ValueRotation,
orientationDegrees: number
) => void
): number {
return NativeReanimatedModule.registerSensor(
const sensorContainer = getSensorContainer();
return sensorContainer.registerSensor(
sensorType,
interval,
iosReferenceFrame,
config,
makeShareableCloneRecursive(eventHandler)
);
}

export function unregisterSensor(listenerId: number): void {
return NativeReanimatedModule.unregisterSensor(listenerId);
export function initializeSensor(
sensorType: SensorType,
config: SensorConfig
): SharedValue<Value3D | ValueRotation> {
const sensorContainer = getSensorContainer();
return sensorContainer.initializeSensor(sensorType, config);
}

export function unregisterSensor(sensorId: number): void {
const sensorContainer = getSensorContainer();
return sensorContainer.unregisterSensor(sensorId);
}

// initialize UI runtime if applicable
Expand Down
2 changes: 2 additions & 0 deletions src/reanimated2/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
} from './commonTypes';
import type { FrameCallbackRegistryUI } from './frameCallback/FrameCallbackRegistryUI';
import type { NativeReanimated } from './NativeReanimated/NativeReanimated';
import type { SensorContainer } from './SensorContainer';
import type {
LayoutAnimationFunction,
LayoutAnimationType,
Expand Down Expand Up @@ -90,6 +91,7 @@ declare global {
var __handleCache: WeakMap<object, any>;
var __callMicrotasks: () => void;
var __mapperRegistry: MapperRegistry;
var __sensorContainer: SensorContainer;
var _maybeFlushUIUpdatesQueue: () => void;
var LayoutAnimationsManager: {
start(
Expand Down
Loading

0 comments on commit eafb345

Please sign in to comment.