Skip to content

Commit

Permalink
Support exceptions in cached content.
Browse files Browse the repository at this point in the history
  • Loading branch information
tmadden committed Oct 2, 2020
1 parent 28abf4e commit b8e2935
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 6 deletions.
54 changes: 49 additions & 5 deletions src/alia/flow/content_caching.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ struct component_caching_data
data_block content_block;
component_container_ptr container;
captured_id content_id;
std::exception_ptr exception;
};

template<class Context, class Component, class... Args>
Expand All @@ -39,15 +40,39 @@ invoke_pure_component(Context ctx, Component&& component, Args&&... args)

if (is_refresh_event(ctx))
{
bool content_traversal_required = container.is_dirty();
// If the component is explicitly marked as dirty or animating, then we
// need to visit it regardless.
bool content_traversal_required
= container.is_dirty() || container.is_animating();

// Construct the combined ID of the context and the arguments to this
// component.
auto content_id
= combine_ids(ref(get_content_id(ctx)), ref(args.value_id())...);
// And check if it still matches.
if (!data->content_id.matches(content_id))
content_traversal_required = true;

scoped_data_block context_setup(ctx, data->context_setup_block);
// If the component code is generating an exception and we have no
// reason to revisit it, just rethrow the exception.
if (!content_traversal_required && data->exception)
{
std::rethrow_exception(data->exception);
}

// This is a bit convoluted, but here we use a fold over the context
// objects to construct a function object that will implement content
// caching for this component. Most context objects don't care about
// caching, so they'll use the default implementation (which doesn't
// contribute any code to the function object). Context objects that
// are managing object trees will insert code to handle that. At the
// heart of the function object is the code to actually invoke the
// component (if necessary).
//
// I'm sure this could be done more directly with some effort, but it's
// fine for now.
//
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) {
Expand All @@ -57,15 +82,34 @@ invoke_pure_component(Context ctx, Component&& component, Args&&... args)
[&]() {
if (content_traversal_required)
{
invoke_content();
data->exception = nullptr;
// Capture the ID of the component in this state.
// Note that even a captured exception is considered a
// "successful" traversal because we know that the
// component is currently just generating that exception.
data->content_id.capture(content_id);
try
{
invoke_content();
}
catch (alia::traversal_abortion&)
{
throw;
}
catch (...)
{
data->exception = std::current_exception();
throw;
}
}
});
// Now execute that function that we just constructed.
invoker();

data->content_id.capture(content_id);
}
else
{
// This is not a refresh event, so all we need to know if whether or
// not this component is on the route to the event target.
if (container.is_on_route())
{
invoke_content();
Expand Down
133 changes: 132 additions & 1 deletion unit_tests/flow/content_caching.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#include <alia/flow/content_caching.hpp>

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

#include <flow/testing.hpp>

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

Expand Down Expand Up @@ -143,3 +144,133 @@ TEST_CASE("content caching", "[flow][content_caching]")
check_log("removing bit0; relocating bit5 into root; ");
REQUIRE(root.object.to_string() == "root(bit5();bit2();)");
}

TEST_CASE("exceptional 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

ALIA_TRY
{
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)
{
auto f = [&](int i) {
return std::string("abcdef").substr(size_t(i));
};
do_object(ctx, lazy_apply(f, n));
}
ALIA_END
},
value(n & 12));
}
ALIA_CATCH(...)
{
do_object(ctx, "error");
}
ALIA_END

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 error into root after bit1; "
"removing bit2; ");
REQUIRE(root.object.to_string() == "root(bit0();bit1();error();)");

n = 0b001110;
refresh_system(sys);
clear_log();
REQUIRE(root.object.to_string() == "root(bit1();error();)");

n = 0b001100;
refresh_system(sys);
clear_log();
REQUIRE(root.object.to_string() == "root(error();)");

n = 0b001101;
refresh_system(sys);
clear_log();
REQUIRE(root.object.to_string() == "root(bit0();error();)");

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

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

0 comments on commit b8e2935

Please sign in to comment.