My entity properties... a bad idea?

My entities for my fog game are just a collection of property classes. There will be thousands of entities so I want to multithread processing of the entities. To avoid race conditions, I plan to defer property updates like so (the earliest property update wins, although this might itself be a property of the property):

`/**

  • A gettable/settable property of an entity.

  • Set ops are parked and applied in the future so entity processing can be safely multithreaded.

  • Created by agslinda on 4/15/14.
    */
    public class Property implements FutureUpdateable {

    public V prop, futureProp;
    //time is an abstract notion of when in a game loop something happens, it’s not related to system time
    long time = 0;

    public V get() {
    return prop;
    }

    public void set(V p, long time) {
    if (futureProp == null || time < this.time) {
    futureProp = p;
    this.time = time;
    }
    }

    @Override
    public void update() {
    prop = futureProp;
    futureProp = null;
    time = 0;
    }
    }
    `

So the Name property looks like this:

`/**

  • A name of a whatsit.
  • Created by agslinda on 4/15/14.
    */
    public class Name extends Property {
    }
    `

There will be other kinds of properties for primitive long, boolean and double values.

There will be thousands of entities so I want to multithread processing of the entities.

Premature optimization. Also thousands of entities isn’t even a blink in the eye of today’s CPUs.

Getters/setters are overrated in my opinion. Start with an immutable class (hey no threading problems) and then expose things as you need them.

Where’s the optimization? This design needlessly (as described) slows things down.

It will be slower in a single thread case, but I think it will enable the multi thread case to work…

Obviously if all the properties are immutable there would be no problem. The question is HOW to expose mutable properties.

you would just clone your whole entity


class Player
{
  final int health; 
  Player(){health = 100;}
  Player(int h){health = h;}
  takeDamage(int dmg){ return new Player(health - dmg); }
}

//...
Player user = new Player();
user = user.takeDamage(15);

Don’t multithread the AIs. You don’t need to. If you think you need to: think again about how to lower the computations per frame. About the only situation I’d think about it would be for a server bank targeting tens of thousands of active players and then I’d use a DSL (probably partly actor based).

Danny02, my original idea was to clone the whole entity, but now I think that would lead to a large amount of garbage collection…

Also, I think your sample is only going to work for single-property entities. Also, I think it will lead to lots of versions of the same instance floating about, which would not be desirable.

The main reasons I am thinking of multithreading are that (1) I am more accurately writing a sim than a game, so I want to go as big as I can go, and (2) the game has a fast forward button that multiplies the computational load.

@Roquen, I expect that AIs will not execute every frame for every entity. No real creature analyzes its situation 30 times a second… :slight_smile: So only a subset of the entities would be doing heavy lifting per frame.

Yeah that’s one thing you can do…distribute across frames. Sleep stuff as much as possible, etc. etc.

I’ve an example/mimimal prototype based variable here: https://github.com/roquendm/JGO-Grabbag/tree/master/src/roquen/wiki/dvar
and of course, it’s targeting single threaded. :wink:

Well, I’m inching towards getting my critters to think:

`
public class Mind {
private static Random R = new Random();
private Mood mood = new Mood();
private int millisToAct;
private World world;
private int countdownToCognition;
private Entity entity;
private Behavior chosenBehavior;

public Mind(int millisToAct, Entity entity, World world) {
    this.millisToAct = millisToAct;
    this.world = world;
    countdownToCognition = R.nextInt(millisToAct);
    this.entity = entity;
}

public void think(int millisElapsed) {
    countdownToCognition -= millisElapsed;
    if (countdownToCognition < 1) {
        //think
        Set<Entity> vicinity = world.getVicinity(entity);
        Set<Entity> awareness = world.getAwareness(entity);
        int priority = 0;
        for (FutureUpdateable futureUpdateable : entity.getProperties()) {
            if (futureUpdateable instanceof Behavior) {
                Behavior behavior = (Behavior) futureUpdateable;
                int thisPriority = behavior.plan(entity, world, mood, vicinity, awareness);
                if (thisPriority > priority) {
                    chosenBehavior = behavior;
                    priority = thisPriority;
                }
            }
        }
        mood.update();
        countdownToCognition = millisToAct;
    }
    if (chosenBehavior != null) {
        chosenBehavior.act(millisElapsed);
    }
}

}
`

EDIT: …it works! I gave my creatures a chatter behavior, and they correctly describe their surroundings.

I think I have a good way to multithread my AIs. I have split the AI into two phases - a planning phase that does heavy computation but changes no state, and an execution phase that makes simple updates according to the plan. The planning phase is multithreaded and the execution phase is single-threaded.

` private List<FutureTask<List>> planningTasks = new LinkedList<FutureTask<List>>();

private void logicFrame() {
    //do expensive planning on multithread
    for (int i = 0; i < planningTasks.size(); i++) {
        planningTasks.get(i).run();
    }
    for (int i = 0; i < planningTasks.size(); i++) {
        try {
            planningTasks.get(i).get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
    //Update entities on a single thread
    for (Mind mind : minds) {
        mind.act(LogicFrameCallable.LOGIC_FRAME_LENGTH);
    }
    //Push new values to buffers, re-sort x,y,z maps
    model.update();
}

`