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;
}
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;
}
return 0;
}
+int find_signal_to_connect(lua_State *L, const Block *block)
+{
+ if (block->signal_type_to_connect == Block::CONNECT_SIGNAL) {
+ return block->signal_to_connect;
+#ifdef HAVE_CEF
+ } else if (block->signal_type_to_connect == Block::CONNECT_CEF) {
+ return block->cef_to_connect->get_card_index();
+#endif
+ } else if (block->signal_type_to_connect == Block::CONNECT_VIDEO) {
+ return block->video_to_connect->get_card_index();
+ } else if (block->signal_type_to_connect == Block::CONNECT_NONE) {
+ luaL_error(L, "An input in a scene was not connected to anything (forgot to call display())");
+ } else {
+ assert(false);
+ }
+ return -1;
+}
+
std::pair<movit::EffectChain *, std::function<void()>>
Scene::get_chain(Theme *theme, lua_State *L, unsigned num, const InputState &input_state)
{
// 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();
for (const auto &index_and_input : instantiation.inputs) {
Block *block = blocks[index_and_input.first];
EffectType chosen_type = current_type(block);
- LiveInputWrapper *input = index_and_input.second;
if (chosen_type == LIVE_INPUT_YCBCR ||
chosen_type == LIVE_INPUT_YCBCR_WITH_DEINTERLACE ||
chosen_type == LIVE_INPUT_YCBCR_PLANAR ||
chosen_type == LIVE_INPUT_BGRA) {
- if (block->signal_type_to_connect == Block::CONNECT_SIGNAL) {
- signals_to_connect.emplace(input, block->signal_to_connect);
-#ifdef HAVE_CEF
- } else if (block->signal_type_to_connect == Block::CONNECT_CEF) {
- signals_to_connect.emplace(input, block->cef_to_connect->get_card_index());
-#endif
- } else if (block->signal_type_to_connect == Block::CONNECT_VIDEO) {
- signals_to_connect.emplace(input, block->video_to_connect->get_card_index());
- } else if (block->signal_type_to_connect == Block::CONNECT_NONE) {
- luaL_error(L, "An input in a scene was not connected to anything (forgot to call display())");
- } else {
- assert(false);
- }
+ LiveInputWrapper *input = index_and_input.second;
+ signals_to_connect.emplace(input, find_signal_to_connect(L, block));
}
}
for (const auto &index_and_input : instantiation.image_inputs) {
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;