poc change notification

This commit is contained in:
Ari Lazier 2016-05-20 15:37:09 -07:00
parent 6d86f8e91f
commit 4d24b29550
13 changed files with 188 additions and 16 deletions

View File

@ -48,6 +48,10 @@ export default class TodoApp extends Component {
// This is a Results object, which will live-update. // This is a Results object, which will live-update.
this.todoLists = todoLists; this.todoLists = todoLists;
todoLists.addListener(function() {
console.log("changed");
});
console.log("registered listener");
// Bind all the methods that we will be passing as props. // Bind all the methods that we will be passing as props.
this.renderScene = this.renderScene.bind(this); this.renderScene = this.renderScene.bind(this);

View File

@ -114,6 +114,7 @@
029048101C0428DF00ABDED4 /* rpc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = rpc.hpp; sourceTree = "<group>"; }; 029048101C0428DF00ABDED4 /* rpc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = rpc.hpp; sourceTree = "<group>"; };
029048351C042A3C00ABDED4 /* platform.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = platform.hpp; sourceTree = "<group>"; }; 029048351C042A3C00ABDED4 /* platform.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = platform.hpp; sourceTree = "<group>"; };
029048381C042A8F00ABDED4 /* platform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platform.mm; sourceTree = "<group>"; }; 029048381C042A8F00ABDED4 /* platform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platform.mm; sourceTree = "<group>"; };
0290934A1CEFA9170009769E /* js_observable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = js_observable.hpp; sourceTree = "<group>"; };
02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; }; 02A3C7A41BC4341500B1A7BE /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmJSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 02B58CBC1AE99CEC009B348C /* RealmJSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RealmJSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; 02B58CCD1AE99D4D009B348C /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
@ -264,6 +265,7 @@
F6874A441CAD2ACD00EEEE36 /* JSC */, F6874A441CAD2ACD00EEEE36 /* JSC */,
F62BF9001CAC72C40022BCDC /* Node */, F62BF9001CAC72C40022BCDC /* Node */,
F62A35141C18E783004A917D /* Object Store */, F62A35141C18E783004A917D /* Object Store */,
0290934A1CEFA9170009769E /* js_observable.hpp */,
F60102F71CBDA6D400EC01BA /* js_collection.hpp */, F60102F71CBDA6D400EC01BA /* js_collection.hpp */,
029048041C0428DF00ABDED4 /* js_list.hpp */, 029048041C0428DF00ABDED4 /* js_list.hpp */,
029048061C0428DF00ABDED4 /* js_realm_object.hpp */, 029048061C0428DF00ABDED4 /* js_realm_object.hpp */,

View File

@ -19,6 +19,7 @@
#pragma once #pragma once
#include "js_class.hpp" #include "js_class.hpp"
#include "js_observable.hpp"
namespace realm { namespace realm {
namespace js { namespace js {
@ -27,7 +28,7 @@ namespace js {
class Collection {}; class Collection {};
template<typename T> template<typename T>
struct CollectionClass : ClassDefinition<T, Collection> { struct CollectionClass : ClassDefinition<T, Collection, ObservableClass<T>> {
std::string const name = "Collection"; std::string const name = "Collection";
}; };

View File

@ -37,6 +37,7 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
using ContextType = typename T::Context; using ContextType = typename T::Context;
using ObjectType = typename T::Object; using ObjectType = typename T::Object;
using ValueType = typename T::Value; using ValueType = typename T::Value;
using FunctionType = typename T::Function;
using Object = js::Object<T>; using Object = js::Object<T>;
using Value = js::Value<T>; using Value = js::Value<T>;
using ReturnValue = js::ReturnValue<T>; using ReturnValue = js::ReturnValue<T>;
@ -59,6 +60,11 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &); static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &);
// observable
static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
std::string const name = "List"; std::string const name = "List";
MethodMap<T> const methods = { MethodMap<T> const methods = {
@ -71,6 +77,9 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
{"filtered", wrap<filtered>}, {"filtered", wrap<filtered>},
{"sorted", wrap<sorted>}, {"sorted", wrap<sorted>},
{"isValid", wrap<is_valid>}, {"isValid", wrap<is_valid>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
}; };
PropertyMap<T> const properties = { PropertyMap<T> const properties = {
@ -231,5 +240,40 @@ void ListClass<T>::is_valid(ContextType ctx, ObjectType this_object, size_t argc
return_value.set(get_internal<T, ListClass<T>>(this_object)->is_valid()); return_value.set(get_internal<T, ListClass<T>>(this_object)->is_valid());
} }
template<typename T>
void ListClass<T>::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto list = get_internal<T, ListClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
Protected<FunctionType> protected_callback(ctx, callback);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
list->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
ValueType arguments[2];
arguments[0] = protected_this;
arguments[1] = Value::from_undefined(protected_ctx);
Function<T>::call(protected_ctx, protected_callback, protected_this, 2, arguments);
}, (size_t)(ValueType)callback);
}
template<typename T>
void ListClass<T>::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto list = get_internal<T, ListClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
list->remove_notification_callback((size_t)(ValueType)callback);
}
template<typename T>
void ListClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
auto list = get_internal<T, ListClass<T>>(this_object);
list->remove_all_notification_callbacks();
}
} // js } // js
} // realm } // realm

35
src/js_observable.hpp Normal file
View File

@ -0,0 +1,35 @@
////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#pragma once
#include "js_class.hpp"
namespace realm {
namespace js {
// Empty class that merely serves as useful type for now.
class Observable {};
template<typename T>
struct ObservableClass : ClassDefinition<T, Observable> {
std::string const name = "Observable";
};
} // js
} // realm

View File

@ -28,6 +28,7 @@
#include "js_list.hpp" #include "js_list.hpp"
#include "js_results.hpp" #include "js_results.hpp"
#include "js_schema.hpp" #include "js_schema.hpp"
#include "js_observable.hpp"
#include "shared_realm.hpp" #include "shared_realm.hpp"
#include "binding_context.hpp" #include "binding_context.hpp"
@ -124,7 +125,7 @@ void set_default_path(std::string path);
void delete_all_realms(); void delete_all_realms();
template<typename T> template<typename T>
class RealmClass : public ClassDefinition<T, SharedRealm> { class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
using GlobalContextType = typename T::GlobalContext; using GlobalContextType = typename T::GlobalContext;
using ContextType = typename T::Context; using ContextType = typename T::Context;
using FunctionType = typename T::Function; using FunctionType = typename T::Function;

View File

@ -34,6 +34,7 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
using ContextType = typename T::Context; using ContextType = typename T::Context;
using ObjectType = typename T::Object; using ObjectType = typename T::Object;
using ValueType = typename T::Value; using ValueType = typename T::Value;
using FunctionType = typename T::Function;
using Object = js::Object<T>; using Object = js::Object<T>;
using Value = js::Value<T>; using Value = js::Value<T>;
using ReturnValue = js::ReturnValue<T>; using ReturnValue = js::ReturnValue<T>;
@ -57,6 +58,11 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &); static void sorted(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &); static void is_valid(ContextType, ObjectType, size_t, const ValueType [], ReturnValue &);
// observable
static void add_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_listener(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void remove_all_listeners(ContextType, ObjectType, size_t, const ValueType[], ReturnValue &);
std::string const name = "Results"; std::string const name = "Results";
MethodMap<T> const methods = { MethodMap<T> const methods = {
@ -64,6 +70,9 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
{"filtered", wrap<filtered>}, {"filtered", wrap<filtered>},
{"sorted", wrap<sorted>}, {"sorted", wrap<sorted>},
{"isValid", wrap<is_valid>}, {"isValid", wrap<is_valid>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
}; };
PropertyMap<T> const properties = { PropertyMap<T> const properties = {
@ -240,5 +249,40 @@ void ResultsClass<T>::is_valid(ContextType ctx, ObjectType this_object, size_t a
return_value.set(get_internal<T, ResultsClass<T>>(this_object)->is_valid()); return_value.set(get_internal<T, ResultsClass<T>>(this_object)->is_valid());
} }
template<typename T>
void ResultsClass<T>::add_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto list = get_internal<T, ResultsClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
Protected<FunctionType> protected_callback(ctx, callback);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
list->add_notification_callback([=](CollectionChangeSet change_set, std::exception_ptr exception) {
ValueType arguments[2];
arguments[0] = protected_this;
arguments[1] = Value::from_undefined(protected_ctx);
Function<T>::call(protected_ctx, protected_callback, protected_this, 2, arguments);
}, (size_t)(ValueType)callback);
}
template<typename T>
void ResultsClass<T>::remove_listener(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto results = get_internal<T, ResultsClass<T>>(this_object);
auto callback = Value::validated_to_function(ctx, arguments[0]);
results->remove_notification_callback((size_t)(ValueType)callback);
}
template<typename T>
void ResultsClass<T>::remove_all_listeners(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
auto results = get_internal<T, ResultsClass<T>>(this_object);
results->remove_all_notification_callbacks();
}
} // js } // js
} // realm } // realm

View File

@ -165,22 +165,19 @@ CollectionNotifier::~CollectionNotifier()
unregister(); unregister();
} }
size_t CollectionNotifier::add_callback(CollectionChangeCallback callback) size_t CollectionNotifier::add_callback(CollectionChangeCallback callback, size_t token)
{ {
m_realm->verify_thread(); m_realm->verify_thread();
auto next_token = [=] { std::lock_guard<std::mutex> lock(m_callback_mutex);
size_t token = 0; if (token == 0) {
for (auto& callback : m_callbacks) { for (auto& callback : m_callbacks) {
if (token <= callback.token) { if (token <= callback.token) {
token = callback.token + 1; token = callback.token + 1;
} }
} }
return token; }
};
std::lock_guard<std::mutex> lock(m_callback_mutex);
auto token = next_token();
m_callbacks.push_back({std::move(callback), token, false}); m_callbacks.push_back({std::move(callback), token, false});
if (m_callback_index == npos) { // Don't need to wake up if we're already sending notifications if (m_callback_index == npos) { // Don't need to wake up if we're already sending notifications
Realm::Internal::get_coordinator(*m_realm).send_commit_notifications(); Realm::Internal::get_coordinator(*m_realm).send_commit_notifications();
@ -216,6 +213,12 @@ void CollectionNotifier::remove_callback(size_t token)
} }
} }
void CollectionNotifier::remove_all_callbacks()
{
m_callbacks.clear();
m_have_callbacks = false;
}
void CollectionNotifier::unregister() noexcept void CollectionNotifier::unregister() noexcept
{ {
std::lock_guard<std::mutex> lock(m_realm_mutex); std::lock_guard<std::mutex> lock(m_realm_mutex);

View File

@ -109,11 +109,12 @@ public:
// Add a callback to be called each time the collection changes // Add a callback to be called each time the collection changes
// This can only be called from the target collection's thread // This can only be called from the target collection's thread
// Returns a token which can be passed to remove_callback() // Returns a token which can be passed to remove_callback()
size_t add_callback(CollectionChangeCallback callback); size_t add_callback(CollectionChangeCallback callback, size_t token = 0);
// Remove a previously added token. The token is no longer valid after // Remove a previously added token. The token is no longer valid after
// calling this function and must not be used again. This function can be // calling this function and must not be used again. This function can be
// called from any thread. // called from any thread.
void remove_callback(size_t token); void remove_callback(size_t token);
void remove_all_callbacks();
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// API for RealmCoordinator to manage running things and calling callbacks // API for RealmCoordinator to manage running things and calling callbacks

View File

@ -184,12 +184,27 @@ size_t hash<realm::List>::operator()(realm::List const& list) const
} }
} }
NotificationToken List::add_notification_callback(CollectionChangeCallback cb) size_t List::add_notification_callback(CollectionChangeCallback cb, size_t token)
{ {
verify_attached(); verify_attached();
if (!m_notifier) { if (!m_notifier) {
m_notifier = std::make_shared<ListNotifier>(m_link_view, m_realm); m_notifier = std::make_shared<ListNotifier>(m_link_view, m_realm);
RealmCoordinator::register_notifier(m_notifier); RealmCoordinator::register_notifier(m_notifier);
} }
return {m_notifier, m_notifier->add_callback(std::move(cb))}; return m_notifier->add_callback(std::move(cb), token);
}
NotificationToken List::add_notification_callback(CollectionChangeCallback cb)
{
return {m_notifier, add_notification_callback(cb, 0)};
}
void List::remove_notification_callback(size_t token)
{
m_notifier->remove_callback(token);
}
void List::remove_all_notification_callbacks()
{
m_notifier->remove_all_callbacks();
} }

View File

@ -74,6 +74,9 @@ public:
bool operator==(List const& rgt) const noexcept; bool operator==(List const& rgt) const noexcept;
NotificationToken add_notification_callback(CollectionChangeCallback cb); NotificationToken add_notification_callback(CollectionChangeCallback cb);
size_t add_notification_callback(CollectionChangeCallback cb, size_t token);
void remove_notification_callback(size_t token);
void remove_all_notification_callbacks();
// These are implemented in object_accessor.hpp // These are implemented in object_accessor.hpp
template <typename ValueType, typename ContextType> template <typename ValueType, typename ContextType>

View File

@ -497,6 +497,22 @@ NotificationToken Results::add_notification_callback(CollectionChangeCallback cb
return {m_notifier, m_notifier->add_callback(std::move(cb))}; return {m_notifier, m_notifier->add_callback(std::move(cb))};
} }
void Results::add_notification_callback(CollectionChangeCallback cb, size_t token)
{
prepare_async();
m_notifier->add_callback(std::move(cb), token);
}
void Results::remove_notification_callback(size_t token)
{
m_notifier->remove_callback(token);
}
void Results::remove_all_notification_callbacks()
{
m_notifier->remove_all_callbacks();
}
bool Results::is_in_table_order() const bool Results::is_in_table_order() const
{ {
switch (m_mode) { switch (m_mode) {

View File

@ -180,6 +180,9 @@ public:
// and then rerun after each commit (if needed) and redelivered if it changed // and then rerun after each commit (if needed) and redelivered if it changed
NotificationToken async(std::function<void (std::exception_ptr)> target); NotificationToken async(std::function<void (std::exception_ptr)> target);
NotificationToken add_notification_callback(CollectionChangeCallback cb); NotificationToken add_notification_callback(CollectionChangeCallback cb);
void add_notification_callback(CollectionChangeCallback cb, size_t token);
void remove_notification_callback(size_t token);
void remove_all_notification_callbacks();
bool wants_background_updates() const { return m_wants_background_updates; } bool wants_background_updates() const { return m_wants_background_updates; }