Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <worklets/SharedItems/Shareables.h>
#include "WorkletStore.h"

using namespace facebook;

Expand Down Expand Up @@ -57,9 +58,11 @@ jsi::Value makeShareableClone(
auto object = value.asObject(rt);

jsi::PropNameID prop = workletCodePropName(rt);
if (object.hasProperty(rt, prop)) {
jsi::Value code = object.getProperty(rt, prop);
shareable = std::make_shared<ShareableString>(code.asString(rt).utf8(rt));
if (object.hasProperty(rt, prop)) { // Worklet function
auto code = object.getProperty(rt, prop).asString(rt).utf8(rt);
double hash = object.getProperty(rt,jsi::String::createFromUtf8(rt,"hash")).asNumber();
WorkletStore::getInstance().set(hash, code);
shareable = std::make_shared<ShareableString>("");
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to return anything

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to return anything

} else if (!object.getProperty(rt, "__workletHash").isUndefined()) {
shareable = std::make_shared<ShareableWorklet>(rt, object);
} else if (!object.getProperty(rt, "__init").isUndefined()) {
Expand Down
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gemini's work

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// WorkletStore.cpp
// Pods
//
// Created by Alexander Pataridze on 29.03.25.
//

#include "WorkletStore.h" // Include the header definition
#include <utility> // For potential use of std::move if needed

// --- Singleton Implementation ---
// Provides the actual instance storage and retrieval logic.
// Meyers' Singleton pattern guarantees thread-safe initialization since C++11.
WorkletStore& WorkletStore::getInstance() {
// The 'static' variable is initialized only once, the first time this function is called.
static WorkletStore instance;
return instance;
}

// --- Method Implementations ---

void WorkletStore::set(double key, const std::string& value) {
// Acquire an exclusive lock that automatically releases when 'lock' goes out of scope.
std::lock_guard<std::mutex> lock(storeMutex_);
// Insert or update the key-value pair in the map.
store_[key] = value;
}

std::string WorkletStore::get(double key) const {
// Acquire a lock (needed even for reading to prevent data races with writes).
std::lock_guard<std::mutex> lock(storeMutex_);
// Find the key in the map.
auto it = store_.find(key);
if (it != store_.end()) {
// Key found, return the value wrapped in std::optional.
return it->second;
}
// Key not found, return an empty std::optional.
return "";
}

bool WorkletStore::remove(double key) {
// Acquire an exclusive lock.
std::lock_guard<std::mutex> lock(storeMutex_);
// Attempt to erase the key. std::unordered_map::erase returns the number
// of elements removed (0 or 1 for maps with unique keys).
return store_.erase(key) > 0;
}

bool WorkletStore::contains(double key) const {
// Acquire a lock.
std::lock_guard<std::mutex> lock(storeMutex_);
// Check if the key exists. map::count returns 1 if the key exists, 0 otherwise.
return store_.count(key) > 0;
// Alternative: return store_.find(key) != store_.end();
}

void WorkletStore::clear() {
// Acquire an exclusive lock.
std::lock_guard<std::mutex> lock(storeMutex_);
// Remove all elements from the map.
store_.clear();
}

size_t WorkletStore::size() const {
// Acquire a lock.
std::lock_guard<std::mutex> lock(storeMutex_);
// Return the current number of elements in the map.
return store_.size();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//
// WorkletMap.h
// Pods
//
// Created by Alexander Pataridze on 29.03.25.
//

#pragma once // Prevents multiple inclusions of the header

#include <string>
#include <unordered_map> // Efficient hash-based map
#include <mutex> // For thread safety
#include <optional> // To safely return values that might not exist
#include <shared_mutex> // For potential read-write optimization (optional)

// A thread-safe singleton class for storing key-value pairs (string -> string).
class WorkletStore {
public:
// --- Singleton Access ---

/**
* @brief Gets the single instance of the WorkletStore. Thread-safe initialization.
* @return Reference to the WorkletStore instance.
*/
static WorkletStore& getInstance();

// --- Deleted Constructors/Assignments (Singleton Pattern) ---
// Prevent copying and moving to enforce single instance
WorkletStore(const WorkletStore&) = delete;
WorkletStore& operator=(const WorkletStore&) = delete;
WorkletStore(WorkletStore&&) = delete;
WorkletStore& operator=(WorkletStore&&) = delete;

// --- Public Interface ---

/**
* @brief Sets (inserts or updates) the value for a given key. Thread-safe.
* @param key The key to set.
* @param value The value to associate with the key.
*/
void set(double key, const std::string& value);

/**
* @brief Gets the value associated with a given key. Thread-safe.
* @param key The key to look up.
* @return An std::optional<std::string> containing the value if the key exists,
* otherwise std::nullopt.
*/
std::string get(double key) const; // const because it reads

/**
* @brief Removes a key-value pair from the store. Thread-safe.
* @param key The key to remove.
* @return true if an element was removed, false otherwise.
*/
bool remove(double key);

/**
* @brief Checks if the store contains a specific key. Thread-safe.
* @param key The key to check for.
* @return true if the key exists, false otherwise.
*/
bool contains(double key) const; // const because it reads

/**
* @brief Removes all key-value pairs from the store. Thread-safe.
*/
void clear();

/**
* @brief Gets the number of key-value pairs currently in the store. Thread-safe.
* @return The number of elements.
*/
size_t size() const; // const because it reads

private:
// --- Private Members ---

// Private constructor: enforce singleton access via getInstance()
WorkletStore() = default;

// Private destructor (can be defaulted if no special cleanup needed)
~WorkletStore() = default;

// The underlying map storing the data
std::unordered_map<double, std::string> store_;

// Mutex to protect access to the store_ map from concurrent threads.
// 'mutable' allows locking even in 'const' methods like get() and contains().
mutable std::mutex storeMutex_;

// --- Optional Optimization (Advanced) ---
// For high-contention scenarios with many more reads than writes,
// a std::shared_mutex can sometimes offer better performance.
// mutable std::shared_mutex storeSharedMutex_;
// Use std::lock_guard<std::shared_mutex> for writes (exclusive lock).
// Use std::shared_lock<std::shared_mutex> for reads (shared lock).
// For simplicity, we'll stick with the standard std::mutex here.
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <worklets/WorkletRuntime/ReanimatedHermesRuntime.h>
#include <worklets/SharedItems/WorkletStore.h>

// Only include this file in Hermes-enabled builds as some platforms (like tvOS)
// don't support hermes and it causes the compilation to fail.
Expand Down Expand Up @@ -80,34 +81,33 @@ ReanimatedHermesRuntime::ReanimatedHermesRuntime(
jsQueue->quitSynchronous();
#endif // HERMES_ENABLE_DEBUGGER

#ifndef NDEBUG
facebook::hermes::HermesRuntime *wrappedRuntime = runtime_.get();
jsi::Value evalWithSourceMap = jsi::Function::createFromHostFunction(
*runtime_,
jsi::PropNameID::forAscii(*runtime_, "evalWithSourceMap"),
3,
[wrappedRuntime](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count) -> jsi::Value {
auto code = std::make_shared<const jsi::StringBuffer>(
args[0].asString(rt).utf8(rt));
std::string sourceURL;
if (count > 1 && args[1].isString()) {
sourceURL = args[1].asString(rt).utf8(rt);
}
std::shared_ptr<const jsi::Buffer> sourceMap;
if (count > 2 && args[2].isString()) {
sourceMap = std::make_shared<const jsi::StringBuffer>(
args[2].asString(rt).utf8(rt));
}
return wrappedRuntime->evaluateJavaScriptWithSourceMap(
code, sourceMap, sourceURL);
});
runtime_->global().setProperty(
*runtime_, "evalWithSourceMap", evalWithSourceMap);
#endif // NDEBUG

facebook::hermes::HermesRuntime *wrappedRuntime = runtime_.get();
jsi::Value evalFromHashValue = jsi::Function::createFromHostFunction(
*runtime_,
jsi::PropNameID::forAscii(*runtime_, "evalFromHashValue"),
3,
[wrappedRuntime](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count) -> jsi::Value {
auto code = std::make_shared<const jsi::StringBuffer>("("+WorkletStore::getInstance().get(args[0].asNumber())+"\n)");
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grab the function from the store


std::string sourceURL;
if (count > 1 && args[1].isString()) {
sourceURL = args[1].asString(rt).utf8(rt);
}
std::shared_ptr<const jsi::Buffer> sourceMap;
if (count > 2 && args[2].isString()) {
sourceMap = std::make_shared<const jsi::StringBuffer>(
args[2].asString(rt).utf8(rt));
}
return wrappedRuntime->evaluateJavaScriptWithSourceMap(
code, sourceMap, sourceURL);
});
runtime_->global().setProperty(
*runtime_, "evalFromHashValue", evalFromHashValue);
}

ReanimatedHermesRuntime::~ReanimatedHermesRuntime() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,6 @@ static std::shared_ptr<jsi::Runtime> makeRuntime(
}
}


std::string getJsValueUnpackerString() {
return "(function valueUnpacker_Exodus_valueUnpackerJs1(objectToUnpack,category,remoteFunctionName){let workletsCache=global.__workletsCache;let handleCache=global.__handleCache;if(workletsCache===undefined){workletsCache=global.__workletsCache=new Map();handleCache=global.__handleCache=new WeakMap();}const workletHash=objectToUnpack.__workletHash;if(workletHash!==undefined){let workletFun=workletsCache.get(workletHash);if(workletFun===undefined){const initData=objectToUnpack.__initData;if(global.evalWithSourceMap){workletFun=global.evalWithSourceMap('('+initData.__reanimated_workletCodeWrapper+'\\n)',initData.location,initData.sourceMap);}else if(global.evalWithSourceUrl){workletFun=global.evalWithSourceUrl('('+initData.__reanimated_workletCodeWrapper+'\\n)',\"worklet_\"+workletHash);}else{workletFun=eval('('+initData.__reanimated_workletCodeWrapper+'\\n)');}workletsCache.set(workletHash,workletFun);}const functionInstance=workletFun.bind(objectToUnpack);objectToUnpack._recur=functionInstance;return functionInstance;}else if(objectToUnpack.__init!==undefined){let value=handleCache.get(objectToUnpack);if(value===undefined){value=objectToUnpack.__init();handleCache.set(objectToUnpack,value);}return value;}else if(category==='RemoteFunction'){const fun=function(){const label=remoteFunctionName?\"function \"+remoteFunctionName:'anonymous function';throw new Error(\"[Reanimated] Tried to synchronously call a non-worklet \"+label+\" on the UI thread.\\nSee https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#tried-to-synchronously-call-a-non-worklet-function-on-the-ui-thread for more details.\");};fun.__remoteFunction=objectToUnpack;return fun;}else{throw new Error(\"[Reanimated] Data type in category \"+category+\" not recognized by value unpacker: \"+_toString(objectToUnpack)+\".\");}}\n)";
}

WorkletRuntime::WorkletRuntime(
jsi::Runtime &rnRuntime,
const std::shared_ptr<MessageQueueThread> &jsQueue,
Expand All @@ -78,7 +73,7 @@ WorkletRuntime::WorkletRuntime(
WorkletRuntimeCollector::install(rt);
WorkletRuntimeDecorator::decorate(rt, name, jsScheduler);

auto codeBuffer = std::make_shared<const jsi::StringBuffer>(getJsValueUnpackerString());
auto codeBuffer = std::make_shared<const jsi::StringBuffer>("(" + valueUnpackerCode + "\n)");
auto valueUnpacker = rt.evaluateJavaScript(codeBuffer, "valueUnpacker")
.asObject(rt)
.asFunction(rt);
Expand Down
5 changes: 3 additions & 2 deletions packages/react-native-reanimated/plugin/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions packages/react-native-reanimated/plugin/src/workletFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,18 @@ export function makeWorkletFactory(
true
);

const reanimatedSecretCodeProperty = objectProperty(
const workletHashProperty = objectProperty(
stringLiteral('hash'),
numericLiteral(workletHash)
);

const reanimatedWorkletCodeProperty = objectProperty(
identifier('__reanimated_workletCodeWrapper'),
objectExpression([workletCodeProperty])
objectExpression([workletCodeProperty, workletHashProperty])
Copy link
Copy Markdown
Author

@alexandrius alexandrius Mar 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add hash to our wrapper for easy access in Shareables.CPP

);

const initDataObjectExpression = objectExpression([
reanimatedSecretCodeProperty,
reanimatedWorkletCodeProperty,
]);

// When testing with jest I noticed that environment variables are set later
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
} from '../commonTypes';
import { jsVersion } from '../platform-specific/jsVersion';
import type { WorkletRuntime } from '../runtimes';
import { getValueUnpackerCode } from '../valueUnpacker';
import { isFabric } from '../PlatformChecker';
import type React from 'react';
import { getShadowNodeWrapperFromRef } from '../fabricUtils';
Expand Down Expand Up @@ -85,7 +86,8 @@ export class NativeReanimated {
}
global._REANIMATED_VERSION_JS = jsVersion;
if (global.__reanimatedModuleProxy === undefined) {
ReanimatedModule?.installTurboModule('');
const valueUnpackerCode = getValueUnpackerCode();
ReanimatedModule?.installTurboModule(valueUnpackerCode);
}
if (global.__reanimatedModuleProxy === undefined) {
throw new ReanimatedError(
Expand Down
8 changes: 5 additions & 3 deletions packages/react-native-reanimated/src/privateGlobals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ declare global {
var _REANIMATED_VERSION_JS: string | undefined;
var __reanimatedModuleProxy: NativeReanimatedModule | undefined;
var __callGuardDEV: typeof callGuardDEV | undefined;
var evalWithSourceMap:
| ((js: string, sourceURL: string, sourceMap: string) => any)
| undefined;
var evalFromHashValue: (
js: string,
sourceURL?: string,
sourceMap?: string
) => any;
var evalWithSourceUrl: ((js: string, sourceURL: string) => any) | undefined;
var _log: (value: unknown) => void;
var _toString: (value: unknown) => string;
Expand Down
Loading