Skip to content

Commit

Permalink
Generalize content caching.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmadden committed Oct 2, 2020
1 parent 109205f commit 7064d66
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 180 deletions.
16 changes: 7 additions & 9 deletions src/alia/context/interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,6 @@ ALIA_ADD_DIRECT_TAGGED_DATA_ACCESS(context_storage, event_traversal_tag, event)
ALIA_ADD_DIRECT_TAGGED_DATA_ACCESS(context_storage, data_traversal_tag, data)
ALIA_ADD_DIRECT_TAGGED_DATA_ACCESS(context_storage, timing_tag, timing)

// Context objects implement this interface if they provide state that might
// influence the component-level application code.
struct stateful_context_object
{
virtual id_interface const&
value_id()
= 0;
};

// the context interface wrapper
template<class Contents>
struct context_interface
Expand Down Expand Up @@ -101,6 +92,13 @@ get_storage_object(context_interface<Contents> ctx)
return *ctx.contents_.storage;
}

template<class Contents>
auto
get_structural_collection(context_interface<Contents> ctx)
{
return ctx.contents_;
}

template<class Tag, class Contents, class Object>
auto
add_context_object(context_interface<Contents> ctx, Object& object)
Expand Down
77 changes: 77 additions & 0 deletions src/alia/flow/content_caching.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#ifndef ALIA_FLOW_CONTENT_CACHING_HPP
#define ALIA_FLOW_CONTENT_CACHING_HPP

#include <alia/context/interface.hpp>
#include <alia/flow/components.hpp>
#include <alia/flow/data_graph.hpp>

namespace alia {

template<class Object, class Content>
auto
implement_alia_content_caching(context, Object&, bool, Content content)
{
return content;
}

struct component_caching_data
{
data_block context_setup_block;
data_block content_block;
component_container_ptr container;
captured_id args_id;
};

template<class Context, class Component, class... Args>
void
invoke_pure_component(Context ctx, Component&& component, Args&&... args)
{
component_caching_data* data;
if (get_data(ctx, &data))
data->container.reset(new component_container);

scoped_component_container container(ctx, &data->container);

auto invoke_content = [&]() {
scoped_data_block content_block(ctx, data->content_block);
component(ctx, std::forward<Args>(args)...);
};

if (is_refresh_event(ctx))
{
bool content_traversal_required = container.is_dirty();

auto args_id = combine_ids(ref(args.value_id())...);
if (!data->args_id.matches(args_id))
content_traversal_required = true;

scoped_data_block context_setup(ctx, data->context_setup_block);

auto invoker = alia::fold_over_collection(
get_structural_collection(ctx),
[&](auto, auto& object, auto content) {
return implement_alia_content_caching(
ctx, object, content_traversal_required, content);
},
[&]() {
if (content_traversal_required)
{
invoke_content();
}
});
invoker();

data->args_id.capture(args_id);
}
else
{
if (container.is_on_route())
{
invoke_content();
}
}
}

} // namespace alia

#endif
52 changes: 26 additions & 26 deletions src/alia/flow/object_trees.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,9 @@ struct scoped_tree_cacher
scoped_tree_cacher(
tree_traversal<Object>& traversal,
tree_caching_data<Object>& data,
id_interface const& content_id,
bool force_update)
bool content_traversal_required)
{
begin(traversal, data, content_id, force_update);
begin(traversal, data, content_traversal_required);
}
~scoped_tree_cacher()
{
Expand All @@ -263,30 +262,22 @@ struct scoped_tree_cacher
begin(
tree_traversal<Object>& traversal,
tree_caching_data<Object>& data,
id_interface const& content_id,
bool force_update)
bool content_traversal_required)
{
traversal_ = &traversal;
data_ = &data;
content_traversal_required_ = force_update;
content_traversal_required_ = content_traversal_required;

// If the content ID changes, we know we have to refresh the contents.
if (!data.content_id.matches(content_id))
content_traversal_required_ = true;

if (content_traversal_required_)
if (content_traversal_required)
{
// If we're updating the contents, capture the content ID now
// (while it's still valid) so we can store it in end().
content_id_.capture(content_id);

// Also record the current value of the tree traversal's next_ptr.
// We're going to traverse the content, so record where we started
// inserting it into the tree.
predecessor_ = traversal.next_ptr;
}
else if (data.subtree_head)
{
// There's no need to refresh, but we have cached content, so we
// need to splice it in...
// There's no need to actually traverse the content, but we do have
// cached content, so we need to splice it in...

// Check to see if we're inserting this where we expected.
if (data.subtree_head->prev_ == traversal.next_ptr)
Expand Down Expand Up @@ -364,28 +355,37 @@ struct scoped_tree_cacher
data_->subtree_head = *predecessor_;
data_->subtree_tail = traversal_->next_ptr;
data_->last_sibling = traversal_->last_sibling;
data_->content_id = std::move(content_id_);
}

traversal_ = nullptr;
}
}

bool
content_traversal_required() const
{
return content_traversal_required_;
}

private:
tree_traversal<Object>* traversal_;
tree_caching_data<Object>* data_;
bool content_traversal_required_;
captured_id content_id_;
tree_node<Object>** predecessor_;
uncaught_exception_detector exception_detector_;
};

template<class Object, class Content>
auto
implement_alia_content_caching(
context ctx,
tree_traversal<Object>& traversal,
bool content_traversal_required,
Content content)
{
tree_caching_data<Object>* data;
get_data(ctx, &data);
return [=, &traversal, &data] {
scoped_tree_cacher<Object> cacher(
traversal, *data, content_traversal_required);
content();
};
}

} // namespace alia

#endif
145 changes: 145 additions & 0 deletions unit_tests/flow/content_caching.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#include <alia/flow/content_caching.hpp>

#include <alia/signals/basic.hpp>
#include <alia/signals/operators.hpp>

#include <flow/testing.hpp>

TEST_CASE("content caching", "[flow][content_caching]")
{
clear_log();

int n = 0;

tree_node<test_object> root;
root.object.name = "root";

auto controller = [&](test_context ctx) {
ALIA_IF(n & 1)
{
do_object(ctx, "bit0");
}
ALIA_END

ALIA_IF(n & 2)
{
do_object(ctx, "bit1");
}
ALIA_END

// Objects inside named blocks aren't necessarily deleted in order, so
// this tests different code paths.
naming_context nc(ctx);
if (n & 32)
{
named_block nb(nc, make_id(32));
do_object(ctx, "bit5");
}

invoke_pure_component(
ctx,
[&](auto ctx, auto n) {
the_log << "traversing cached content; ";

ALIA_IF(n & 4)
{
do_object(ctx, "bit2");
}
ALIA_END

ALIA_IF(n & 8)
{
do_object(ctx, "bit3");
}
ALIA_END
},
value(n & 12));

ALIA_IF(n & 16)
{
do_object(ctx, "bit4");
}
ALIA_END
};

alia::system sys;
initialize_system(sys, [&](context vanilla_ctx) {
tree_traversal<test_object> traversal;
auto ctx = detail::add_context_object<tree_traversal_tag>(
vanilla_ctx, traversal);
if (is_refresh_event(ctx))
{
traverse_object_tree(traversal, root, [&]() { controller(ctx); });
}
else
{
controller(ctx);
}
});

n = 0b000000;
refresh_system(sys);
check_log("traversing cached content; ");
REQUIRE(root.object.to_string() == "root()");

n = 0b000011;
refresh_system(sys);
check_log(
"relocating bit0 into root; "
"relocating bit1 into root after bit0; ");
REQUIRE(root.object.to_string() == "root(bit0();bit1();)");

n = 0b000010;
refresh_system(sys);
check_log("removing bit0; ");
REQUIRE(root.object.to_string() == "root(bit1();)");

n = 0b001111;
refresh_system(sys);
check_log(
"relocating bit0 into root; "
"traversing cached content; "
"relocating bit2 into root after bit1; "
"relocating bit3 into root after bit2; ");
REQUIRE(root.object.to_string() == "root(bit0();bit1();bit2();bit3();)");

n = 0b001110;
refresh_system(sys);
check_log("removing bit0; ");
REQUIRE(root.object.to_string() == "root(bit1();bit2();bit3();)");

n = 0b101110;
refresh_system(sys);
check_log("relocating bit5 into root after bit1; ");
REQUIRE(root.object.to_string() == "root(bit1();bit5();bit2();bit3();)");

n = 0b101100;
refresh_system(sys);
check_log("removing bit1; ");
REQUIRE(root.object.to_string() == "root(bit5();bit2();bit3();)");

n = 0b101101;
refresh_system(sys);
check_log("relocating bit0 into root; ");
REQUIRE(root.object.to_string() == "root(bit0();bit5();bit2();bit3();)");

n = 0b001101;
refresh_system(sys);
check_log(
"relocating bit2 into root after bit0; "
"relocating bit3 into root after bit2; "
"removing bit5; ");
REQUIRE(root.object.to_string() == "root(bit0();bit2();bit3();)");

n = 0b000101;
refresh_system(sys);
check_log(
"traversing cached content; "
"removing bit3; ");
REQUIRE(root.object.to_string() == "root(bit0();bit2();)");

n = 0b100100;
refresh_system(sys);
check_log("removing bit0; relocating bit5 into root; ");
REQUIRE(root.object.to_string() == "root(bit5();bit2();)");
}

0 comments on commit 7064d66

Please sign in to comment.