Skip to content

Commit

Permalink
Shareable value rewrite (#3722)
Browse files Browse the repository at this point in the history
## Description

This PR contains a new implementation of the "shareable value" concept – one of the lowest level key concepts behind shared values/worklets etc.

There were many reasons for attempting this rewrite, but the main objective was to address memory-related issues rooted in that core part and difficult to fix due to an inherent complexity of that bit of the codebase. Below, I list a couple of reasons providing some more details description:

1. The aforementioned memory issues can be best noted on #2824 – the root cause of that and similar issue lies in the way we reference objects between the two runtimes. Due to the fact we use JSI's host objects and direct references it turns out that a lot of objects can outlast garbage collection because, for example they are referenced on the secondary runtime where garbage collection didn't run yet. Moreover, JSI objects have their own, simplified version of GC that has to run as well. As a result, for some objects to be cleaned up properly, we needed to run GC on the main JS runtime, then on the UI runtime and once again on the main JS runtime.
2. Secondary objective for the rewrite was to address the maintainability of the codebase part being rewritten. After investigating the issue mentioned above we concluded that it is too difficult and risky to try to fix it given the architectural choices we made in the past.
3. As a side-effect of the things being rewritten we expect the library to perform better. When tracking performance issues in the past, we detected that a lot of issues are due to an excessive communication over JSI. JSI is a lot quicker compared to calling things over "the bridge", however it still incurs some performance penalty that is significant enough on lower level devices even at a level of couple hundred calls. This issue has became apparent to us at some point, but the existing architecture wouldn't allow us to address it in a easy way. As an example we can bring up the issue with worklet functions that, before this rewrite, were represented as JSI's HostFunctions. As a result, calling worklet from on the UI thread would require a JSI roundtrip, and that has became problematic as we expanded the codebase and had one more logic extracted out into smaller worklets. This rewrite has been architected to optimize the number of cross-JSI calls.
4. The new core makes it possible to implement some new and often requested functionalities. Specifically, the ability to make complex shareable objects, that is, to be able to append to a shared array or add/remove entries from a shared map as opposed to always having to copy the whole new object with the modifications applied. Adding this also helps to improve some bits internally, specifically the case of shared styles, where we have a single animated style object assigned to multiple views. Before, we'd use an immutable array that'd represent the list of so-called view descriptors. With this rewrite we can add and remove entries without a need to copy the whole descriptors array as we mount new views.

The description of this PR is also a work in progress and will update sections of this description as the work progresses.

## Changes

This PR changes a lot of things under the hood. The main change lies in a way we create and reference shareable values. As part of this change the whole "notion" of shareable values is removed from the core and implemented on top of a different abstraction. We now allow for "shareable" data to provide an initializer function which is called on the UI runtime in order to instantiate data that then will be accessed by worklets. In addition, we avoid keeping JSI object cache on c++ side, this prevents issues with them being referenced for longer than necessary and hence was a root cause of memory related issues (leaks -- but these weren't actual leaks as triggering GC several times would result in a complete cleanup).
  • Loading branch information
kmagiera authored Nov 29, 2022
1 parent bf37a2f commit 99573c3
Show file tree
Hide file tree
Showing 75 changed files with 2,347 additions and 3,175 deletions.
69 changes: 32 additions & 37 deletions Common/cpp/AnimatedSensor/AnimatedSensorModule.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
#include "AnimatedSensorModule.h"
#include "MutableValue.h"
#include "ValueWrapper.h"

#include <memory>
#include <utility>

#include "Shareables.h"

namespace reanimated {

AnimatedSensorModule::AnimatedSensorModule(
const PlatformDepMethodsHolder &platformDepMethodsHolder,
RuntimeManager *runtimeManager)
const PlatformDepMethodsHolder &platformDepMethodsHolder)
: platformRegisterSensorFunction_(platformDepMethodsHolder.registerSensor),
platformUnregisterSensorFunction_(
platformDepMethodsHolder.unregisterSensor),
runtimeManager_(runtimeManager) {}
platformDepMethodsHolder.unregisterSensor) {}

AnimatedSensorModule::~AnimatedSensorModule() {
// It is called during app reload because app reload doesn't call hooks
Expand All @@ -25,41 +23,38 @@ AnimatedSensorModule::~AnimatedSensorModule() {

jsi::Value AnimatedSensorModule::registerSensor(
jsi::Runtime &rt,
const jsi::Value &sensorType,
const std::shared_ptr<JSRuntimeHelper> &runtimeHelper,
const jsi::Value &sensorTypeValue,
const jsi::Value &interval,
const jsi::Value &sensorDataContainer) {
std::shared_ptr<ShareableValue> sensorsData = ShareableValue::adapt(
rt, sensorDataContainer.getObject(rt), runtimeManager_);
auto &mutableObject =
ValueWrapper::asMutableValue(sensorsData->valueContainer);
const jsi::Value &sensorDataHandler) {
SensorType sensorType = static_cast<SensorType>(sensorTypeValue.asNumber());

std::function<void(double[])> setter;
if (sensorType.asNumber() == SensorType::ROTATION_VECTOR) {
setter = [&, mutableObject](double newValues[]) {
jsi::Runtime &runtime = *runtimeManager_->runtime.get();
jsi::Object value(runtime);
value.setProperty(runtime, "qx", newValues[0]);
value.setProperty(runtime, "qy", newValues[1]);
value.setProperty(runtime, "qz", newValues[2]);
value.setProperty(runtime, "qw", newValues[3]);
value.setProperty(runtime, "yaw", newValues[4]);
value.setProperty(runtime, "pitch", newValues[5]);
value.setProperty(runtime, "roll", newValues[6]);
mutableObject->setValue(runtime, std::move(value));
};
} else {
setter = [&, mutableObject](double newValues[]) {
jsi::Runtime &runtime = *runtimeManager_->runtime.get();
jsi::Object value(runtime);
value.setProperty(runtime, "x", newValues[0]);
value.setProperty(runtime, "y", newValues[1]);
value.setProperty(runtime, "z", newValues[2]);
mutableObject->setValue(runtime, std::move(value));
};
}
auto shareableHandler = extractShareableOrThrow(rt, sensorDataHandler);
auto uiRuntime = runtimeHelper->uiRuntime();

int sensorId = platformRegisterSensorFunction_(
sensorType.asNumber(), interval.asNumber(), setter);
sensorType, interval.asNumber(), [=](double newValues[]) {
jsi::Runtime &rt = *uiRuntime;
auto handler =
shareableHandler->getJSValue(rt).asObject(rt).asFunction(rt);
if (sensorType == SensorType::ROTATION_VECTOR) {
jsi::Object value(rt);
value.setProperty(rt, "qx", newValues[0]);
value.setProperty(rt, "qy", newValues[1]);
value.setProperty(rt, "qz", newValues[2]);
value.setProperty(rt, "qw", newValues[3]);
value.setProperty(rt, "yaw", newValues[4]);
value.setProperty(rt, "pitch", newValues[5]);
value.setProperty(rt, "roll", newValues[6]);
handler.call(rt, value);
} else {
jsi::Object value(rt);
value.setProperty(rt, "x", newValues[0]);
value.setProperty(rt, "y", newValues[1]);
value.setProperty(rt, "z", newValues[2]);
handler.call(rt, value);
}
});
if (sensorId != -1) {
sensorsIds_.insert(sensorId);
}
Expand Down
7 changes: 4 additions & 3 deletions Common/cpp/AnimatedSensor/AnimatedSensorModule.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#pragma once

#include <jsi/jsi.h>
#include <memory>
#include <unordered_set>

#include "PlatformDepMethodsHolder.h"
#include "RuntimeManager.h"
#include "Shareables.h"

namespace reanimated {

Expand All @@ -22,16 +24,15 @@ class AnimatedSensorModule {
std::unordered_set<int> sensorsIds_;
RegisterSensorFunction platformRegisterSensorFunction_;
UnregisterSensorFunction platformUnregisterSensorFunction_;
RuntimeManager *runtimeManager_;

public:
AnimatedSensorModule(
const PlatformDepMethodsHolder &platformDepMethodsHolder,
RuntimeManager *runtimeManager);
const PlatformDepMethodsHolder &platformDepMethodsHolder);
~AnimatedSensorModule();

jsi::Value registerSensor(
jsi::Runtime &rt,
const std::shared_ptr<JSRuntimeHelper> &runtimeHelper,
const jsi::Value &sensorType,
const jsi::Value &interval,
const jsi::Value &sensorDataContainer);
Expand Down
76 changes: 76 additions & 0 deletions Common/cpp/LayoutAnimations/LayoutAnimationsManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include "LayoutAnimationsManager.h"
#include "Shareables.h"

#include <utility>

namespace reanimated {

void LayoutAnimationsManager::configureAnimation(
int tag,
const std::string &type,
std::shared_ptr<Shareable> config) {
auto lock = std::unique_lock<std::mutex>(animationsMutex_);
if (type == "entering") {
enteringAnimations_[tag] = config;
} else if (type == "exiting") {
exitingAnimations_[tag] = config;
} else if (type == "layout") {
layoutAnimations_[tag] = config;
}
}

bool LayoutAnimationsManager::hasLayoutAnimation(
int tag,
const std::string &type) {
auto lock = std::unique_lock<std::mutex>(animationsMutex_);
if (type == "entering") {
return enteringAnimations_.find(tag) != enteringAnimations_.end();
} else if (type == "exiting") {
return exitingAnimations_.find(tag) != exitingAnimations_.end();
} else if (type == "layout") {
return layoutAnimations_.find(tag) != layoutAnimations_.end();
}
return false;
}

void LayoutAnimationsManager::clearLayoutAnimationConfig(int tag) {
auto lock = std::unique_lock<std::mutex>(animationsMutex_);
enteringAnimations_.erase(tag);
exitingAnimations_.erase(tag);
layoutAnimations_.erase(tag);
}

void LayoutAnimationsManager::startLayoutAnimation(
jsi::Runtime &rt,
int tag,
const std::string &type,
const jsi::Object &values) {
std::shared_ptr<Shareable> config, viewShareable;
{
auto lock = std::unique_lock<std::mutex>(animationsMutex_);
if (type == "entering") {
config = enteringAnimations_[tag];
} else if (type == "exiting") {
config = exitingAnimations_[tag];
} else if (type == "layout") {
config = layoutAnimations_[tag];
}
}

// TODO: cache the following!!
jsi::Value layoutAnimationRepositoryAsValue =
rt.global()
.getPropertyAsObject(rt, "global")
.getProperty(rt, "LayoutAnimationsManager");
jsi::Function startAnimationForTag =
layoutAnimationRepositoryAsValue.getObject(rt).getPropertyAsFunction(
rt, "start");
startAnimationForTag.call(
rt,
jsi::Value(tag),
jsi::String::createFromAscii(rt, type),
values,
config->getJSValue(rt));
}

} // namespace reanimated
41 changes: 41 additions & 0 deletions Common/cpp/LayoutAnimations/LayoutAnimationsManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include "ErrorHandler.h"
#include "Shareables.h"

#include <jsi/jsi.h>
#include <stdio.h>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>

namespace reanimated {

using namespace facebook;

class LayoutAnimationsManager {
public:
void configureAnimation(
int tag,
const std::string &type,
std::shared_ptr<Shareable> config);
bool hasLayoutAnimation(int tag, const std::string &type);
void startLayoutAnimation(
jsi::Runtime &rt,
int tag,
const std::string &type,
const jsi::Object &values);
void clearLayoutAnimationConfig(int tag);

private:
std::unordered_map<int, std::shared_ptr<Shareable>> enteringAnimations_;
std::unordered_map<int, std::shared_ptr<Shareable>> exitingAnimations_;
std::unordered_map<int, std::shared_ptr<Shareable>> layoutAnimations_;
mutable std::mutex
animationsMutex_; // Protects `enteringAnimations_`, `exitingAnimations_`,
// `layoutAnimations_` and `viewSharedValues_`.
};

} // namespace reanimated
127 changes: 0 additions & 127 deletions Common/cpp/LayoutAnimations/LayoutAnimationsProxy.cpp

This file was deleted.

Loading

0 comments on commit 99573c3

Please sign in to comment.