X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=nageru%2Fscene.cpp;fp=nageru%2Fscene.cpp;h=04f15ed827437fb87b8340d17ee77aa9cc513119;hb=1924bea4fde26067d8ee81cfd12f2dd66c5b2cad;hp=16948826343126b87af65e111ae4fa3d78e0d29f;hpb=948d715655a84b93d8292e64731ea3c32b45deb7;p=nageru 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;