]> git.sesse.net Git - nageru/commitdiff
Support disabling optional effects if a given other effect is _enabled_.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 28 Jul 2019 20:06:24 +0000 (22:06 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 28 Jul 2019 20:07:15 +0000 (22:07 +0200)
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
nageru/scene.h
nageru/theme.cpp

index 16948826343126b87af65e111ae4fa3d78e0d29f..04f15ed827437fb87b8340d17ee77aa9cc513119 100644 (file)
@@ -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;
index 77155479adeb2f25280a0b822b67de0f2044541f..8db75540559cfa29bf45611319e19380bb56fc1c 100644 (file)
@@ -88,7 +88,21 @@ struct Block {
 
        std::vector<EffectBlueprint *> alternatives;  // Must all have the same amount of inputs. Pointers to make things easier for Lua.
        std::vector<Index> inputs;  // One for each input of alternatives[0] (ie., typically 0 or 1, occasionally 2).
-       std::vector<Index> 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 <condition>, 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<Disabler> 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);
index 9506294dcea2c11c3d5367a1bed4bd3c7fba45fc..78b0fde35cd2b075e2482f921776da71ade955f9 100644 (file)
@@ -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 },