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.todoLists = todoLists;
todoLists.addListener(function() {
console.log("changed");
});
console.log("registered listener");
// Bind all the methods that we will be passing as props.
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>"; };
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>"; };
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; };
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; };
@ -264,6 +265,7 @@
F6874A441CAD2ACD00EEEE36 /* JSC */,
F62BF9001CAC72C40022BCDC /* Node */,
F62A35141C18E783004A917D /* Object Store */,
0290934A1CEFA9170009769E /* js_observable.hpp */,
F60102F71CBDA6D400EC01BA /* js_collection.hpp */,
029048041C0428DF00ABDED4 /* js_list.hpp */,
029048061C0428DF00ABDED4 /* js_realm_object.hpp */,

View File

@ -19,6 +19,7 @@
#pragma once
#include "js_class.hpp"
#include "js_observable.hpp"
namespace realm {
namespace js {
@ -27,7 +28,7 @@ namespace js {
class Collection {};
template<typename T>
struct CollectionClass : ClassDefinition<T, Collection> {
struct CollectionClass : ClassDefinition<T, Collection, ObservableClass<T>> {
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 ObjectType = typename T::Object;
using ValueType = typename T::Value;
using FunctionType = typename T::Function;
using Object = js::Object<T>;
using Value = js::Value<T>;
using ReturnValue = js::ReturnValue<T>;
@ -58,7 +59,12 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
static void filtered(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 &);
// 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";
MethodMap<T> const methods = {
@ -71,6 +77,9 @@ struct ListClass : ClassDefinition<T, realm::List, CollectionClass<T>> {
{"filtered", wrap<filtered>},
{"sorted", wrap<sorted>},
{"isValid", wrap<is_valid>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
};
PropertyMap<T> const properties = {
@ -230,6 +239,41 @@ template<typename T>
void ListClass<T>::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
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
} // 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_results.hpp"
#include "js_schema.hpp"
#include "js_observable.hpp"
#include "shared_realm.hpp"
#include "binding_context.hpp"
@ -124,7 +125,7 @@ void set_default_path(std::string path);
void delete_all_realms();
template<typename T>
class RealmClass : public ClassDefinition<T, SharedRealm> {
class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
using GlobalContextType = typename T::GlobalContext;
using ContextType = typename T::Context;
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 ObjectType = typename T::Object;
using ValueType = typename T::Value;
using FunctionType = typename T::Function;
using Object = js::Object<T>;
using Value = js::Value<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 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";
MethodMap<T> const methods = {
@ -64,6 +70,9 @@ struct ResultsClass : ClassDefinition<T, realm::Results, CollectionClass<T>> {
{"filtered", wrap<filtered>},
{"sorted", wrap<sorted>},
{"isValid", wrap<is_valid>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
};
PropertyMap<T> const properties = {
@ -239,6 +248,41 @@ template<typename T>
void ResultsClass<T>::is_valid(ContextType ctx, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
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
} // realm

View File

@ -165,22 +165,19 @@ CollectionNotifier::~CollectionNotifier()
unregister();
}
size_t CollectionNotifier::add_callback(CollectionChangeCallback callback)
size_t CollectionNotifier::add_callback(CollectionChangeCallback callback, size_t token)
{
m_realm->verify_thread();
auto next_token = [=] {
size_t token = 0;
std::lock_guard<std::mutex> lock(m_callback_mutex);
if (token == 0) {
for (auto& callback : m_callbacks) {
if (token <= callback.token) {
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});
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();
@ -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
{
std::lock_guard<std::mutex> lock(m_realm_mutex);

View File

@ -109,12 +109,13 @@ public:
// Add a callback to be called each time the collection changes
// This can only be called from the target collection's thread
// 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
// calling this function and must not be used again. This function can be
// called from any thread.
void remove_callback(size_t token);
void remove_all_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();
if (!m_notifier) {
m_notifier = std::make_shared<ListNotifier>(m_link_view, m_realm);
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,7 +74,10 @@ public:
bool operator==(List const& rgt) const noexcept;
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
template <typename ValueType, typename ContextType>
void add(ContextType ctx, ValueType value);

View File

@ -497,6 +497,22 @@ NotificationToken Results::add_notification_callback(CollectionChangeCallback 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
{
switch (m_mode) {

View File

@ -180,7 +180,10 @@ public:
// and then rerun after each commit (if needed) and redelivered if it changed
NotificationToken async(std::function<void (std::exception_ptr)> target);
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; }
// Returns whether the rows are guaranteed to be in table order.