Merge branch '1372-used-memory-grows-quadratic-in-the-number-of-triggered-spells' into 'master'

Resolve "Used memory grows quadratic in the number of triggered spells"

Closes #1372

See merge request core-developers/forge!2756
This commit is contained in:
Michael Kamensky
2020-04-25 17:36:50 +00:00
11 changed files with 49 additions and 80 deletions

View File

@@ -1352,7 +1352,7 @@ public class ComputerUtil {
if (valid.contains("Creature.YouCtrl") if (valid.contains("Creature.YouCtrl")
|| valid.contains("Other+YouCtrl") ) { || valid.contains("Other+YouCtrl") ) {
final SpellAbility sa = t.getTriggeredSA(); final SpellAbility sa = t.getOverridingAbility();
if (sa != null && sa.getApi() == ApiType.Pump && sa.hasParam("KW") if (sa != null && sa.getApi() == ApiType.Pump && sa.hasParam("KW")
&& sa.getParam("KW").contains("Haste")) { && sa.getParam("KW").contains("Haste")) {
return true; return true;

View File

@@ -894,6 +894,9 @@ public class Game {
public void clearCaches() { public void clearCaches() {
spabCache.clear(); spabCache.clear();
cardCache.clear(); cardCache.clear();
lastStateBattlefield.clear();
lastStateGraveyard.clear();
//playerCache.clear(); //playerCache.clear();
} }

View File

@@ -1056,23 +1056,6 @@ public class Card extends GameEntity implements Comparable<Card> {
} }
} }
public final Object getTriggeringObject(final AbilityKey typeIn) {
Object triggered = null;
if (!currentState.getTriggers().isEmpty()) {
for (final Trigger t : currentState.getTriggers()) {
final SpellAbility sa = t.getTriggeredSA();
if (sa == null) {
continue;
}
triggered = sa.hasTriggeringObject(typeIn) ? sa.getTriggeringObject(typeIn) : null;
if (triggered != null) {
break;
}
}
}
return triggered;
}
public final int getSunburstValue() { public final int getSunburstValue() {
return sunburstValue; return sunburstValue;
} }

View File

@@ -680,7 +680,6 @@ public class CardFactory {
wrapperAbility.setTrigger(true); wrapperAbility.setTrigger(true);
wrapperAbility.setMandatory(sa.isMandatory()); wrapperAbility.setMandatory(sa.isMandatory());
wrapperAbility.setDescription(wrapperAbility.getStackDescription()); wrapperAbility.setDescription(wrapperAbility.getStackDescription());
t.setTriggeredSA(wrapperAbility);
return wrapperAbility; return wrapperAbility;
} }

View File

@@ -34,7 +34,6 @@ import forge.game.GameEntity;
import forge.game.GameEntityCounterTable; import forge.game.GameEntityCounterTable;
import forge.game.GameLogEntryType; import forge.game.GameLogEntryType;
import forge.game.ability.AbilityFactory; import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType; import forge.game.ability.ApiType;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
@@ -954,11 +953,6 @@ public class CardFactoryUtil {
return doXMath(c.getRegeneratedThisTurn(), m, c); return doXMath(c.getRegeneratedThisTurn(), m, c);
} }
// TriggeringObjects
if (sq[0].startsWith("Triggered")) {
return doXMath(xCount((Card) c.getTriggeringObject(AbilityKey.Card), sq[0].substring(9)), m, c);
}
if (sq[0].contains("YourStartingLife")) { if (sq[0].contains("YourStartingLife")) {
return doXMath(cc.getStartingLife(), m, c); return doXMath(cc.getStartingLife(), m, c);
} }

View File

@@ -6,6 +6,7 @@ import forge.card.MagicColor;
import forge.game.Direction; import forge.game.Direction;
import forge.game.Game; import forge.game.Game;
import forge.game.GameEntity; import forge.game.GameEntity;
import forge.game.GameObject;
import forge.game.ability.AbilityKey; import forge.game.ability.AbilityKey;
import forge.game.ability.AbilityUtils; import forge.game.ability.AbilityUtils;
import forge.game.card.CardPredicates.Presets; import forge.game.card.CardPredicates.Presets;
@@ -15,7 +16,6 @@ import forge.game.keyword.Keyword;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.OptionalCost; import forge.game.spellability.OptionalCost;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.trigger.Trigger;
import forge.game.zone.Zone; import forge.game.zone.Zone;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
import forge.util.Expressions; import forge.util.Expressions;
@@ -392,30 +392,20 @@ public class CardProperty {
} }
} else if (property.startsWith("AttachedTo")) { } else if (property.startsWith("AttachedTo")) {
final String restriction = property.split("AttachedTo ")[1]; final String restriction = property.split("AttachedTo ")[1];
if (restriction.equals("Targeted")) {
if (!source.getCurrentState().getTriggers().isEmpty()) { if (card.getEntityAttachedTo() == null) {
for (final Trigger t : source.getCurrentState().getTriggers()) {
final SpellAbility sa = t.getTriggeredSA();
final CardCollectionView cards = AbilityUtils.getDefinedCards(source, "Targeted", sa);
for (final Card c : cards) {
if (card.getEquipping() != c && !c.equals(card.getEntityAttachedTo())) {
return false; return false;
} }
if (!card.getEntityAttachedTo().isValid(restriction, sourceController, source, spellAbility)) {
boolean found = false;
for (final GameObject o : AbilityUtils.getDefinedObjects(source, restriction, spellAbility)) {
if (o.equals(card.getEntityAttachedTo())) {
found = true;
break;
} }
} }
} else { if (!found) {
for (final SpellAbility sa : source.getCurrentState().getNonManaAbilities()) {
final CardCollectionView cards = AbilityUtils.getDefinedCards(source, "Targeted", sa);
for (final Card c : cards) {
if (card.getEquipping() == c || c.equals(card.getEntityAttachedTo())) { // handle multiple targets
return true;
}
}
}
return false;
}
} else {
if ((card.getEntityAttachedTo() == null || !card.getEntityAttachedTo().isValid(restriction, sourceController, source, spellAbility))) {
return false; return false;
} }
} }
@@ -856,15 +846,6 @@ public class CardProperty {
} }
} }
return false; return false;
case "TriggeredCard":
final Object triggeringObject = source.getTriggeringObject(AbilityKey.fromString(restriction.substring("Triggered".length())));
if (!(triggeringObject instanceof Card)) {
return false;
}
if (card.sharesCardTypeWith((Card) triggeringObject)) {
return true;
}
return false;
case "EachTopLibrary": case "EachTopLibrary":
final CardCollection cards = new CardCollection(); final CardCollection cards = new CardCollection();
for (Player p : game.getPlayers()) { for (Player p : game.getPlayers()) {

View File

@@ -1996,4 +1996,24 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public void setXManaCostPaid(final Integer n) { public void setXManaCostPaid(final Integer n) {
xManaCostPaid = n; xManaCostPaid = n;
} }
public void removeFromGame() {
if (getHostCard() == null) {
return;
}
getHostCard().getGame().removeSpellAbility(this);
if (subAbility != null) {
subAbility.removeFromGame();
}
for (AbilitySub sa : additionalAbilities.values()) {
sa.removeFromGame();
}
for (List<AbilitySub> list : additionalAbilityLists.values()) {
for (AbilitySub sa : list) {
sa.removeFromGame();
}
}
}
} }

View File

@@ -29,7 +29,6 @@ import forge.game.card.CardState;
import forge.game.phase.PhaseHandler; import forge.game.phase.PhaseHandler;
import forge.game.phase.PhaseType; import forge.game.phase.PhaseType;
import forge.game.player.Player; import forge.game.player.Player;
import forge.game.spellability.Ability;
import forge.game.spellability.OptionalCost; import forge.game.spellability.OptionalCost;
import forge.game.spellability.SpellAbility; import forge.game.spellability.SpellAbility;
import forge.game.zone.ZoneType; import forge.game.zone.ZoneType;
@@ -472,27 +471,6 @@ public abstract class Trigger extends TriggerReplacementBase {
this.id = id; this.id = id;
} }
private Ability triggeredSA;
/**
* Gets the triggered sa.
*
* @return the triggered sa
*/
public final Ability getTriggeredSA() {
return this.triggeredSA;
}
/**
* Sets the triggered sa.
*
* @param sa
* the triggered sa to set
*/
public void setTriggeredSA(final Ability sa) {
this.triggeredSA = sa;
}
public void addRemembered(Object o) { public void addRemembered(Object o) {
this.triggerRemembered.add(o); this.triggerRemembered.add(o);
} }

View File

@@ -628,7 +628,6 @@ public class TriggerHandler {
else { else {
game.getStack().addSimultaneousStackEntry(wrapperAbility); game.getStack().addSimultaneousStackEntry(wrapperAbility);
} }
regtrig.setTriggeredSA(wrapperAbility);
regtrig.triggerRun(); regtrig.triggerRun();

View File

@@ -563,4 +563,10 @@ public class WrappedAbility extends Ability {
public void setXManaCostPaid(final Integer n) { public void setXManaCostPaid(final Integer n) {
sa.setXManaCostPaid(n); sa.setXManaCostPaid(n);
} }
@Override
public void removeFromGame() {
super.removeFromGame();
getHostCard().getGame().removeSpellAbility(this.getWrappedAbility());
}
} }

View File

@@ -638,7 +638,13 @@ public class MagicStack /* extends MyObservable */ implements Iterable<SpellAbil
frozenStack.remove(si); frozenStack.remove(si);
game.updateStackForView(); game.updateStackForView();
SpellAbility sa = si.getSpellAbility(true); SpellAbility sa = si.getSpellAbility(true);
sa.setLastStateBattlefield(CardCollection.EMPTY);
sa.setLastStateGraveyard(CardCollection.EMPTY);
game.fireEvent(new GameEventSpellRemovedFromStack(sa)); game.fireEvent(new GameEventSpellRemovedFromStack(sa));
if (sa.isTrigger()) {
sa.removeFromGame();
}
} }
public final void remove(final Card c) { public final void remove(final Card c) {