From 1924bea4fde26067d8ee81cfd12f2dd66c5b2cad Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 28 Jul 2019 22:06:24 +0200 Subject: [PATCH] Support disabling optional effects if a given other effect is _enabled_. There are some restrictions (see the comments), but this is generally useful if two effects are mutually exclusive, e.g., an overlay that can be at one of many different points in the chain. --- nageru/scene.cpp | 88 +++++++++++++++++++++++++++++++++++++++++++----- nageru/scene.h | 17 +++++++++- nageru/theme.cpp | 1 + 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/nageru/scene.cpp b/nageru/scene.cpp index 1694882..04f15ed 100644 --- a/nageru/scene.cpp +++ b/nageru/scene.cpp @@ -97,19 +97,42 @@ bitset<256> Scene::find_disabled_blocks(size_t chain_idx) const do { find_disabled_blocks(chain_idx, blocks.size() - 1, /*currently_disabled=*/false, &ret); prev = ret; + + // Propagate DISABLE_IF_OTHER_DISABLED constraints (we can always do this). for (Block *block : blocks) { + if (ret.test(block->idx)) continue; // Already disabled. + EffectType chosen_type = block->alternatives[block->chosen_alternative(chain_idx)]->effect_type; - if (ret.test(block->idx) || chosen_type == IDENTITY_EFFECT) continue; // Already disabled. + if (chosen_type == IDENTITY_EFFECT) { + ret.set(block->idx); + continue; + } - for (Block::Index disabler_idx : block->disablers) { - Block *disabler = blocks[disabler_idx]; - EffectType chosen_type = disabler->alternatives[disabler->chosen_alternative(chain_idx)]->effect_type; - if (ret.test(disabler->idx) || chosen_type == IDENTITY_EFFECT) { + for (const Block::Disabler &disabler : block->disablers) { + Block *other = blocks[disabler.block_idx]; + EffectType chosen_type = other->alternatives[other->chosen_alternative(chain_idx)]->effect_type; + bool other_disabled = ret.test(disabler.block_idx) || chosen_type == IDENTITY_EFFECT; + if (other_disabled && disabler.condition == Block::Disabler::DISABLE_IF_OTHER_DISABLED) { ret.set(block->idx); break; } } } + + // We cannot propagate DISABLE_IF_OTHER_ENABLED in all cases; + // the problem is that if A is disabled if B is enabled, + // then we cannot disable A unless we actually know for sure + // that B _is_ enabled. (E.g., imagine that B is disabled + // if C is enabled -- we couldn't disable A before we knew if + // C was enabled or not!) + // + // We could probably fix a fair amount of these, but the + // primary use case for DISABLE_IF_OTHER_ENABLED is really + // mutual exclusion; A must be disabled if B is enabled + // _and_ vice versa. These loops cannot be automatically + // resolved; it would depend on what A and B is. Thus, + // we simply declare this kind of constraint to be a promise + // from the user, not something that we'll solve for them. } while (prev != ret); return ret; } @@ -137,15 +160,23 @@ void Scene::find_disabled_blocks(size_t chain_idx, size_t block_idx, bool curren bool Scene::is_noncanonical_chain(size_t chain_idx) const { bitset<256> disabled = find_disabled_blocks(chain_idx); - if (disabled.none()) { - return false; - } assert(blocks.size() < 256); for (size_t block_idx = 0; block_idx < blocks.size(); ++block_idx) { Block *block = blocks[block_idx]; if (disabled.test(block_idx) && block->chosen_alternative(chain_idx) != block->canonical_alternative) { return true; } + + // Test if we're supposed to be disabled by some other block being enabled; + // the disabled bit mask does not fully capture this. + if (!disabled.test(block_idx)) { + for (const Block::Disabler &disabler : block->disablers) { + if (disabler.condition == Block::Disabler::DISABLE_IF_OTHER_ENABLED && + !disabled.test(disabler.block_idx)) { + return true; + } + } + } } return false; } @@ -420,6 +451,23 @@ Scene::get_chain(Theme *theme, lua_State *L, unsigned num, const InputState &inp // the Lua code later. bool is_main_chain = (num == 0); size_t chain_idx = compute_chain_number(is_main_chain); + if (is_noncanonical_chain(chain_idx)) { + // This should be due to promise_to_disable_if_enabled(). Find out what + // happened, to give the user some help. + bitset<256> disabled = find_disabled_blocks(chain_idx); + for (size_t block_idx = 0; block_idx < blocks.size(); ++block_idx) { + Block *block = blocks[block_idx]; + if (disabled.test(block_idx)) continue; + for (const Block::Disabler &disabler : block->disablers) { + if (disabler.condition == Block::Disabler::DISABLE_IF_OTHER_ENABLED && + !disabled.test(disabler.block_idx)) { + fprintf(stderr, "Promise declared at %s violated.\n", disabler.declaration_point.c_str()); + abort(); + } + } + } + assert(false); // Something else happened, seemingly. + } const Scene::Instantiation &instantiation = chains[chain_idx]; EffectChain *effect_chain = instantiation.chain.get(); @@ -717,7 +765,29 @@ int Block_always_disable_if_disabled(lua_State *L) luaL_error(L, "always_disable_if_disabled() with an argument that didn't have an IdentityEffect fallback (try add_optional_effect())"); } - block->disablers.push_back(disabler_block->idx); + // The declaration point isn't actually used, but it's nice for completeness. + block->disablers.push_back(Block::Disabler{ disabler_block->idx, Block::Disabler::DISABLE_IF_OTHER_DISABLED, get_declaration_point(L) }); + + lua_pop(L, 2); + return 0; +} + +int Block_promise_to_disable_if_enabled(lua_State *L) +{ + assert(lua_gettop(L) == 2); + Block *block = *(Block **)luaL_checkudata(L, 1, "Block"); + Block *disabler_block = *(Block **)luaL_checkudata(L, 2, "Block"); + + int my_alternative = find_index_of(block, IDENTITY_EFFECT); + int their_alternative = find_index_of(disabler_block, IDENTITY_EFFECT); + if (my_alternative == -1) { + luaL_error(L, "promise_to_disable_if_enabled() called on something that didn't have an IdentityEffect fallback (try add_optional_effect())"); + } + if (their_alternative == -1) { + luaL_error(L, "promise_to_disable_if_enabled() with an argument that didn't have an IdentityEffect fallback (try add_optional_effect())"); + } + + block->disablers.push_back(Block::Disabler{ disabler_block->idx, Block::Disabler::DISABLE_IF_OTHER_ENABLED, get_declaration_point(L) }); lua_pop(L, 2); return 0; diff --git a/nageru/scene.h b/nageru/scene.h index 7715547..8db7554 100644 --- a/nageru/scene.h +++ b/nageru/scene.h @@ -88,7 +88,21 @@ struct Block { std::vector alternatives; // Must all have the same amount of inputs. Pointers to make things easier for Lua. std::vector inputs; // One for each input of alternatives[0] (ie., typically 0 or 1, occasionally 2). - std::vector disablers; // If any of these are disabled (IdentityEffect chosen), so should this one. + + // If any of these effects are disabled (IdentityEffect chosen) + // or enabled (not chosen) as determined by , so should this one. + struct Disabler { + Index block_idx; + enum { + DISABLE_IF_OTHER_DISABLED, + + // This a promise from the user; ie., we don't disable automatically + // (see comments in find_disabled_blocks()). + DISABLE_IF_OTHER_ENABLED + } condition; + std::string declaration_point; // For error messages. + }; + std::vector disablers; int currently_chosen_alternative = 0; // What alternative to use if the block is disabled. // Points to an alternative with IDENTITY_EFFECT if it exists @@ -125,6 +139,7 @@ int Block_enable(lua_State *L); int Block_enable_if(lua_State *L); int Block_disable(lua_State *L); int Block_always_disable_if_disabled(lua_State *L); +int Block_promise_to_disable_if_enabled(lua_State *L); int Block_set_int(lua_State *L); int Block_set_float(lua_State *L); int Block_set_vec3(lua_State *L); diff --git a/nageru/theme.cpp b/nageru/theme.cpp index 9506294..78b0fde 100644 --- a/nageru/theme.cpp +++ b/nageru/theme.cpp @@ -873,6 +873,7 @@ const luaL_Reg Block_funcs[] = { { "enable_if", Block_enable_if }, { "disable", Block_disable }, { "always_disable_if_disabled", Block_always_disable_if_disabled }, + { "promise_to_disable_if_enabled", Block_promise_to_disable_if_enabled }, { "set_int", Block_set_int }, { "set_float", Block_set_float }, { "set_vec3", Block_set_vec3 }, -- 2.39.2