Closed atkdefender closed 3 years ago
"If I ReplaceComponent many times in one frame, Reactive System only call once. How to fix this" - this is desired behaviour
Reactive Systems are designed to aggregate changes until Execute is called. If you replace a component multiple times, that actually only triggers the system once and the latest data will be read once you call execute
e.ReplacePosition3(new Vector3(0, 0, 0));
e.ReplacePosition3(new Vector3(1, 0, 0));
e.ReplacePosition3(new Vector3(2, 0, 0));
a reactive system with a trigger on Matcher.Position3 will collect the entity and when you call execute it and read the entity's position, you will read new Vector3(2, 0, 0)
e.ReplacePosition3(new Vector3(0, 0, 0)); e.ReplacePosition3(new Vector3(1, 0, 0)); e.ReplacePosition3(new Vector3(2, 0, 0));
a reactive system with a trigger on Matcher.Position3 will collect the entity and when you call execute it and read the entity's position, you will read new Vector3(2, 0, 0)
So that if I have some codes, need to react every time I call replacePosition, even in one frame. The codes should move out of the PositionReactiveSystem?
My original usage is AttackerEntity throw a DamageComponent to BehiterEntity. Then ReactiveSystem deals with the Trigger“damage.add()”. I should find another way to write this.
Reactive Systems are designed to aggregate changes until Execute is called. If you replace a component multiple times, that actually only triggers the system once and the latest data will be read once you call execute
I do some experiments to take it in.
Anything I miss? I had read the wiki. https://github.com/sschmid/Entitas-CSharp/wiki/Systems
My original usage is AttackerEntity throw a DamageComponent to BehiterEntity. Then ReactiveSystem deals with the Trigger“damage.add()”. I should find another way to write this.
This is not how you should do this. There are a problem that in one frame you can receive several damage entries, and you need to count all of them. Because, for example, your entity have 2 health, and it attacked by three different player entities - each deal 1 damage, you need to know who is deal latest damage before entity dies.
Right thing is doing this - make and component that have List of damages that dealed, and make a reactive system that react to changes of that list. And system will determine how much damage is dealed.
// Component
[Umpire]
public sealed class DamageComponent : IComponent {
public List<ADamage> List;
public static implicit operator List<ADamage>(DamageComponent component) => component.List;
public override string ToString() => "Damage";
}
// AbstractDamage
public abstract class ADamage {
public readonly EDamageType Type;
public WeaponId AttackUid;
public InstanceId Applicator;
public float Power;
public float Lifesteal;
public bool IsCritical;
protected ADamage(EDamageType type) { Type = type; }
public abstract float Evaluate(Character target);
}
// Processing damage
public class DamageSystem : ReactiveSystem<UmpireEntity>, ICleanupSystem {
private readonly IClock _clock;
private readonly List<UmpireEntity> _clean = new List<UmpireEntity>();
public DamageSystem(UmpireContext umpire, IClock clock) : base(umpire) {
_clock = clock;
}
protected override ICollector<UmpireEntity> GetTrigger(IContext<UmpireEntity> context)
=> context.CreateCollector(
UmpireMatcher.AllOf(
UmpireMatcher.Character,
UmpireMatcher.Damage,
UmpireMatcher.AppliedDamage
)
);
protected override bool Filter(UmpireEntity entity)
=> entity.hasDamage && entity.hasAppliedDamage && entity.hasCharacter && entity.damage.List.Count > 0;
protected override void Execute(List<UmpireEntity> entities) {
foreach (var entity in entities) {
if (!entity.isDead)
ProcessDamageList(entity);
_clean.Add(entity);
}
}
private void ProcessDamageList(UmpireEntity entity) {
var character = entity.character.Reference;
var shield = 0f;
var health = entity.health.Value;
var bubble = false;
foreach (var damage in entity.damage.List) {
var trueDamage = damage.Evaluate(character);
if (ProcessShieldAndAfterItHaveAnyLeft(entity, ref shield, ref trueDamage))
continue;
bubble = true;
ProcessDamageEntry(entity, damage, ref trueDamage, ref health);
ProcessLifesteal(damage, trueDamage);
// system that determine that entity is dead - next down execution order
if (health.IsZero())
break;
}
if (bubble)
entity.ReplaceAppliedDamage(entity.appliedDamage.List);
}
private static bool ProcessShieldAndAfterItHaveAnyLeft(UmpireEntity entity, ref float shield, ref float trueDamage) {
// to much code for example xD
}
private void ProcessDamageEntry(UmpireEntity entity, ADamage damage, ref float trueDamage, ref float health) {
trueDamage = trueDamage.Min(health);
entity.ApplyHealth(-trueDamage); // Shortcut extension - like entity.appliedHealth.List.Add(-trueDamage); entity.ReplaceAppliedHelath(entity.appliedHealth.List);
// This lis holds all damages that received, and you can determine who kills who and who assists
entity.appliedDamage.List.Add(
new DamageApplied {
Applicator = damage.Applicator.id.Value
AppliedAt = _clock.Time,
Damage = trueDamage,
Type = damage.Type,
IsCritical = damage.IsCritical
}
);
}
private static void ProcessLifesteal(ADamage damage, float trueDamage) {
if (damage.Lifesteal > 0f)
damage.Applicator.ApplyLifesteal(trueDamage * damage.Lifesteal);
}
public void Cleanup() {
foreach (var entity in _clean)
entity.damage.List.Clear();
_clean.Clear();
}
}
My original usage is AttackerEntity throw a DamageComponent to BehiterEntity. Then ReactiveSystem deals with the Trigger“damage.add()”. I should find another way to write this.
This is not how you should do this. There are a problem that in one frame you can receive several damage entries, and you need to count all of them. Because, for example, your entity have 2 health, and it attacked by three different player entities - each deal 1 damage, you need to know who is deal latest damage before entity dies.
Right thing is doing this - make and component that have List of damages that dealed, and make a reactive system that react to changes of that list. And system will determine how much damage is dealed.
// Component [Umpire] public sealed class DamageComponent : IComponent { public List<ADamage> List; public static implicit operator List<ADamage>(DamageComponent component) => component.List; public override string ToString() => "Damage"; }
Thank you so much for sharing your codes to me :D To memory a damage list instead of one damage component, clean it up at every end of the frame. It solved my problem. The “abstract” part also open up my eyes.
Some "ReplaceComponent"s lose, does't call Reactive System.