Skip to content

Commit

Permalink
perf: Cache functions on JS-side instead of C++ (#131)
Browse files Browse the repository at this point in the history
* perf: Cache functions on JS-side instead of C++

* Remove unused .c_str
  • Loading branch information
mrousavy authored Sep 6, 2021
1 parent 9f1c30c commit 7e26464
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 62 deletions.
18 changes: 1 addition & 17 deletions android/src/main/cpp/MmkvHostObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,12 @@ std::vector<jsi::PropNameID> MmkvHostObject::getPropertyNames(jsi::Runtime& rt)

jsi::Value MmkvHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) {
auto propName = propNameId.utf8(runtime);

auto value = functionCache.find(propName);
if (value != functionCache.end()) {
return value->second.getFunction(runtime);
}

jsi::Value newValue = getFunction(runtime, propName);
if (newValue.isUndefined()) {
return jsi::Value::undefined();
}
jsi::Function newFunction = newValue.asObject(runtime).asFunction(runtime);
functionCache.insert({ propName, newFunction.getFunction(runtime) });
return newFunction;
}

jsi::Value MmkvHostObject::getFunction(jsi::Runtime& runtime, const std::string& propName) {
auto funcName = "MMKV." + propName;

if (propName == "set") {
// MMKV.set(key: string, value: string | number | bool)
return jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forAscii(runtime, funcName.c_str()),
jsi::PropNameID::forAscii(runtime, funcName),
2, // key, value
[this](jsi::Runtime& runtime,
const jsi::Value& thisValue,
Expand Down
3 changes: 0 additions & 3 deletions android/src/main/cpp/MmkvHostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

#include <jsi/jsi.h>
#include <MMKV.h>
#include <unordered_map>

using namespace facebook;

Expand All @@ -24,8 +23,6 @@ class JSI_EXPORT MmkvHostObject: public jsi::HostObject {
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;

private:
jsi::Value getFunction(jsi::Runtime& runtime, const std::string& propName);
std::unordered_map<std::string, jsi::Function> functionCache;
MMKV* instance;
std::string* path;
std::string* encryptionKey;
Expand Down
7 changes: 2 additions & 5 deletions ios/MmkvHostObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,18 @@
#import <Foundation/Foundation.h>
#import <jsi/jsi.h>
#import <MMKV/MMKV.h>
#include <unordered_map>

using namespace facebook;

class JSI_EXPORT MmkvHostObject: public jsi::HostObject {
public:
MmkvHostObject(NSString* instanceId, NSString* path, NSString* cryptKey);
~MmkvHostObject();

public:
jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override;
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;

private:
jsi::Value getFunction(jsi::Runtime& runtime, const std::string& propName);
std::unordered_map<std::string, jsi::Function> functionCache;
MMKV* instance;
};
44 changes: 14 additions & 30 deletions ios/MmkvHostObject.mm
Original file line number Diff line number Diff line change
Expand Up @@ -36,36 +36,20 @@

jsi::Value MmkvHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) {
auto propName = propNameId.utf8(runtime);

auto value = functionCache.find(propName);
if (value != functionCache.end()) {
return value->second.getFunction(runtime);
}

jsi::Value newValue = getFunction(runtime, propName);
if (newValue.isUndefined()) {
return jsi::Value::undefined();
}
jsi::Function newFunction = newValue.asObject(runtime).asFunction(runtime);
functionCache.insert({ propName, newFunction.getFunction(runtime) });
return newFunction;
}

jsi::Value MmkvHostObject::getFunction(jsi::Runtime& runtime, const std::string& propName) {
auto funcName = "MMKV." + propName;

if (propName == "set") {
// MMKV.set(key: string, value: string | number | bool)
return jsi::Function::createFromHostFunction(runtime,
jsi::PropNameID::forAscii(runtime, funcName.c_str()),
jsi::PropNameID::forAscii(runtime, funcName),
2, // key, value
[this](jsi::Runtime& runtime,
const jsi::Value& thisValue,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) throw jsi::JSError(runtime, "MMKV::set: First argument ('key') has to be of type string!");
auto keyName = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));

if (arguments[1].isBool()) {
[instance setBool:arguments[1].getBool() forKey:keyName];
} else if (arguments[1].isNumber()) {
Expand All @@ -79,7 +63,7 @@
return jsi::Value::undefined();
});
}

if (propName == "getBoolean") {
// MMKV.getBoolean(key: string)
return jsi::Function::createFromHostFunction(runtime,
Expand All @@ -90,13 +74,13 @@
const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");

auto keyName = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));
auto value = [instance getBoolForKey:keyName];
return jsi::Value(value);
});
}

if (propName == "getString") {
// MMKV.getString(key: string)
return jsi::Function::createFromHostFunction(runtime,
Expand All @@ -107,7 +91,7 @@
const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");

auto keyName = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));
auto value = [instance getStringForKey:keyName];
if (value != nil)
Expand All @@ -116,7 +100,7 @@
return jsi::Value::undefined();
});
}

if (propName == "getNumber") {
// MMKV.getNumber(key: string)
return jsi::Function::createFromHostFunction(runtime,
Expand All @@ -127,13 +111,13 @@
const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");

auto keyName = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));
auto value = [instance getDoubleForKey:keyName];
return jsi::Value(value);
});
}

if (propName == "delete") {
// MMKV.delete(key: string)
return jsi::Function::createFromHostFunction(runtime,
Expand All @@ -144,13 +128,13 @@
const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");

auto keyName = convertJSIStringToNSString(runtime, arguments[0].getString(runtime));
[instance removeValueForKey:keyName];
return jsi::Value::undefined();
});
}

if (propName == "getAllKeys") {
// MMKV.getAllKeys()
return jsi::Function::createFromHostFunction(runtime,
Expand All @@ -164,7 +148,7 @@
return convertNSArrayToJSIArray(runtime, keys);
});
}

if (propName == "clearAll") {
// MMKV.clearAll()
return jsi::Function::createFromHostFunction(runtime,
Expand All @@ -178,6 +162,6 @@
return jsi::Value::undefined();
});
}

return jsi::Value::undefined();
}
32 changes: 25 additions & 7 deletions src/MMKV.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export interface MMKVInterface {
*/
export class MMKV implements MMKVInterface {
private nativeInstance: MMKVInterface;
private functionCache: Partial<MMKVInterface>;

/**
* Creates a new MMKV instance with the given Configuration.
Expand All @@ -95,27 +96,44 @@ export class MMKV implements MMKVInterface {
}
// @ts-expect-error global func is a native JSI func
this.nativeInstance = global.mmkvCreateNewInstance(configuration);
this.functionCache = {};
}

private getFunctionFromCache<T extends keyof MMKVInterface>(
functionName: T
): MMKVInterface[T] {
if (this.functionCache[functionName] == null) {
this.functionCache[functionName] = this.nativeInstance[functionName];
}
return this.functionCache[functionName] as MMKVInterface[T];
}

set(key: string, value: boolean | string | number): void {
return this.nativeInstance.set(key, value);
const func = this.getFunctionFromCache('set');
return func(key, value);
}
getBoolean(key: string): boolean {
return this.nativeInstance.getBoolean(key);
const func = this.getFunctionFromCache('getBoolean');
return func(key);
}
getString(key: string): string | undefined {
return this.nativeInstance.getString(key);
const func = this.getFunctionFromCache('getString');
return func(key);
}
getNumber(key: string): number {
return this.nativeInstance.getNumber(key);
const func = this.getFunctionFromCache('getNumber');
return func(key);
}
delete(key: string): void {
return this.nativeInstance.delete(key);
const func = this.getFunctionFromCache('delete');
return func(key);
}
getAllKeys(): string[] {
return this.nativeInstance.getAllKeys();
const func = this.getFunctionFromCache('getAllKeys');
return func();
}
clearAll(): void {
return this.nativeInstance.clearAll();
const func = this.getFunctionFromCache('clearAll');
return func();
}
}

0 comments on commit 7e26464

Please sign in to comment.