Ah, I understand. In my opinion I think the problem is that the gun is an entity in the first place. Since the gun is under the control of the AI of the ship, it’s actually a component of the ship, not a separate entity. That’ll eliminate the gun entity from the equation at least.
Concerning the target problem, there are a few solutions depending on what you need. The simplest solution is simply to synchronize on the target and modify what you want. You may not want multiple entities to shoot at the same target if the target dies from the first shot. Although entities would be able to detect if the entity they’re firing at has died, the actual firing order will depend on the threads (= semi-random, non-deterministic).
If you require determinism here, it might be possible to completely switch the problem around. I’ll take inspiration from GPUs. GPUs can multithread each pixel since you’re only ever allowed to write to the current pixel. You’re however free to read from whatever texture at whatever point you wish. This is called “gathering”, e.g. gather data from multiple places but only write to one place. You’re doing the opposite since each entity reads from itself but writes to multiple places, AKA “scattering”. It’s possible to switch the problem around so we get gathering instead. Instead of checking which entities the current entity is targeting, you check which entities that are targeting the current entity. That way you can easily apply the damage in a predictable order, but this instead introduces problems with checking if the entities that are firing actually can fire. You can split this up into four passes to get rid of that problem though. For a completely deterministic threaded algorithm, you can do the following:
System 1, targetting: Each entity searches for targets for its guns, validates those targets (checks if they’re shootable) and stores them if targets are found.
System 2, firing AI: Each entity figures out which guns to fire based on the available power but does not actually modify the power yet. When the entity decides to shoot another entity, you schedule a shot by storing an object representing the shot in a unsynchronized list in the firing entity, and also write the shot object to a synchronized list in the target being shot.
System 3, damage updating: Each entity checks its list of shots firing at it, sorts them to guarantee determinism (sort by entity ID or something) and then applies their damage. If the damage is applied successfully, you mark the shot as successful. If the object dies and cannot be damaged anymore, you mark the shot as unsuccessful.
System 4, power draw: Each entity checks each shot that it fired, checks if it was successful and updates the current amount of power based on the cost of the successful shots.
Psuedo code:
System 1
for(Entity e : entities){
for(Gun gun : e.getGuns()){
gun.acquireTarget();
}
}
System 2
for(Entity e : entities){
e.figureOutWhichGunsToFire();
for(Gun gun : e.getWhichGunsToFire()){
Entity target = gun.getTarget();
Shot shot = new Shot(e, target, powerDraw, damage);
e.firedShots.add(shot);
synchronized(target){
target.hits.add(shot);
}
}
}
System 3
for(Entity e : entities){
List<Shot> hits = e.getHits();
sort(hits);
for(Shot hit : hits){
if(e.isAlive()){
e.health -= hit.getDamage();
hit.setSuccessful(true);
}else{
hit.setSuccessful(false);
}
}
}
System 4
for(Entity e : entities){
List<Shot> firedShots = e.getFiredShots();
for(Shot firedShot : firedShots){
if(firedShot.wasSuccessful()){
e.power -= shot.getPowerDraw();
}
}
}
EDIT: System 1 and 2 could be combined into a single system:
for(Entity e : entities){
for(Gun gun : e.getGuns()){
gun.acquireTarget();
}
e.figureOutWhichGunsToFire();
for(Gun gun : e.getWhichGunsToFire()){
Entity target = gun.getTarget();
Shot shot = new Shot(e, target, powerDraw, damage);
e.firedShots.add(shot);
synchronized(target){
target.hits.add(shot);
}
}
}