Skip to content

Next iteration of "wait for completion" framework #1582

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jun 2, 2016

Conversation

nealterrell
Copy link
Collaborator

@nealterrell nealterrell commented Jun 2, 2016

See #1494 for the first part of this discussion.

This PR makes a number of large leaps allowing certain general effects in the game engine to "wait" for other effects to complete before proceeding with their remaining logic. Normally this just happens naturally with function calls, but when functions require user input to complete (via prompts) they return immediately before the prompt is actually dealt with. #1494 solved this problem for a few select Corp Operations; this PR improves the framework and extends it to many other effects, notably damage routines, traces, psi games, and a few run events.

There are a lot of changes, and I'm not 100% confident that there are no breaking changes here. All the tests pass, but we have imperfect coverage.

Fixes #1092, fixes #1533, fixes #1453, fixes #1429, fixes #1208, fixes #1153.

eids

If we want the ability to wait for an effect to complete before resuming other code, then we need to identify every effect that occurs in the engine. So we assign integer identifiers ("eids") to every ability that gets invoked via resolve-ability, plus a few other core functions. When these effects complete, they signal their completion to any "listeners", where a listener is a closure over a function body that wants to resume once the subscribed effect is completed.

Technically an eid is a map with a single key :eid mapped to a unique integer. The state assigns new eids via make-eid.

The effect/req macros of implementing card abilities have been expanded to take a fifth parameter (it is in the third position, after state side) called eid, representing the eid assigned to the resolution of the ability. Some core engine abilities also now take an eid parameter; in general, these functions are the ones I have upgraded to support this framework.

Waiting for an effect to complete

If you want to wait for an effect to complete, you generally use the when-completed macro. when-completed takes two arguments: a single function to execute "asynchronously" (let's call this "the async") and a single function to execute once the first has completed (let's call this "the await"). The async function must be a function that takes state side eid as its first three arguments. when-completed will generate an eid (unless the async already has a third parameter that is an eid), register a closure around the await function that will listen for the completion of the async's eid, then return.

If you have programmed in C# or F#, you may recognize this pattern as what Microsoft calls "async/await".

Examples:

Suppose ability is some card ability we want to resolve, and once that ability is fully completed we want to print a system message.

(when-completed 
  (resolve-ability state side ability card targets)
  (system-msg state side "Ability completed"))

resolve-ability has been upgraded to take an eid parameter, so this works. Once the selected ability has resolved, we will execute the system-msg. (Example: Accelerated Diagnostics will wait for its first operation to fully resolve before giving a prompt to choose the next.)

Suppose we want to invoke the :successful-run event, wait for all handlers to complete, and then proceed with the run/access code.

(when-completed 
  (trigger-event-sync state side :successful-run server)
  (do-access state side server))

trigger-event-sync will trigger an event, but wait for each handler ability to complete before invoking the next, and the whole event will only complete once the last handler completes. Thus, we will only move to do-access once all event handlers for :successful-run have finished. (Example: Bernice Mai's trace will fully resolve before moving to the access phase.)

These cover the two most common uses: waiting for a single ability, and waiting for all event listeners. There are a few other functions that can be awaited, but they are mostly just to implement certain other end-results.

  • play-instant (when the card's effect is complete)
  • corp-install (when the install-effect of the card is complete [currently no such effects?])
  • damage (when the entire damage/prevention sequence completes)
  • register-successful-run (when the event handlers for :successful-run have completed)

When is an effect "completed"?

An effect is completed when its eid is passed to (effect-completed). When this happens depends a little on context:

  • resolve-ability will immediately call effect-completed after invoking the :effect of the ability, UNLESS the ability is marked with :delayed-completion true, OR the ability is a psi, trace, optional, or prompt (choices) ability AND is not marked with :delayed-completion false. Thus, simple abilities do not need to change anything about how they work: Hedge Fund will "complete" after granting its credits.
    • psi abilities will complete when credits are spent and the equal/notequal effect completes
    • traces will complete when credits are spent and the successful/unsuccessful effect completes
    • optionals will complete when the yes/no effect completes
    • prompts will complete when the prompt's ability resolves
  • if an ability uses :delayed-completion true, it is responsible for invoking effect-completed itself at an appropriate time. There are two shortcuts: continue-ability (see below) can resolve a sub-ability using the same eid, and if that sub-ability immediately resolves as above, that will in effect complete the main ability. Or you can use final-effect instead of effect to automatically insert the effect-completed call.
  • damage will complete once the damage is applied (post-prevention effects)

Current uses

I am currently using the framework for these fixes/effects:

  1. Damage: each instance of damage will fully resolve before moving onto the next, including prevention opportunities. This cleans up issues with Deus X preventing multiple sources of damage. It also helps Subcontract or AccDiag into double Scorched Earth, by not allowing the selection of the second Scorched until the first fully resolves.

    This also cleans up Chronos Protocol, by allowing its damage-replacement effect to fully resolve before moving on to any other sources of damage.

  2. On-access effects: if a Corp card has an :access effect, that effect will be resolved before the runner is allowed to trash/steal/continue accessing other cards. Thus, Fetal AI will do its damage routine before Personal Evolution will apply its damage from the steal. Also, the psi game and damage from Psychic Field will complete before PF is trashed and damage from Hostile Infrastructure might be applied.

  3. Successful-run effects: the runner will not move to the access phase until all :successful-run triggers have resolved, so Bernice Mai's trace will complete prior to access, and if the trace fails she will be trashed and not a valid access target.

Other details

  1. when-completed will assign an eid to the async target automatically, but you can assign one yourself by giving an eid as the third parameter to the async function. This can be useful when you want to be specific about what eid the async gets, because you know someone in particular is waiting for that eid.
  2. when-completed will also work with apply.
  3. continue-ability is a new macro that works exactly the same as resolve-ability, except it passes the current eid of whoever called continue-ability as the eid of the ability to be resolved. This is useful when a card's effect calls resolve-ability to "finish" the effect; by using continue-ability instead, the resolved sub-ability keeps the same eid as the "main" ability, and so the sub-ability's completion signifies the completion of the main ability.

@queueseven
Copy link
Collaborator

Sounds awesome!

@mtgred
Copy link
Owner

mtgred commented Jun 2, 2016

Awesome! 👍

@mtgred mtgred merged commit 7275224 into mtgred:master Jun 2, 2016
@nealterrell nealterrell deleted the event-ids branch June 2, 2016 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment