Open Hanmac opened 2 months ago
My favorite use case for this would be spinning off one chooseSpellAbilityToPlay()
call to run as simulation
This would allow some sort of hybrid approach and still improve AI capabilities (fall back to heuristics if no better gamestate score gets found after X seconds)
For the speedup we need to make sure the threads can then run on different cores if that doesn't work by default then
@tool4ever I wanted to use some collection/filter stuff first, because these should have the least amount of side effects
@Agetian stream().parallel()
might do the work for us, but i need to experiment with that
Could multiple AI begin processing their actions in parallel when priority starts being passed around, acting on the assumption that other players are going to just pass? Then either cancel and restart the computation if a player before them takes an action, or keep to their intent if priority gets to them and nothing has changed about the gamestate?
Could multiple AI begin processing their actions in parallel when priority starts being passed around, acting on the assumption that other players are going to just pass? Then either cancel and restart the computation if a player before them takes an action, or keep to their intent if priority gets to them and nothing has changed about the gamestate?
Making "interruptible" Threads is far more into the future
for now, the goal is to optimize smaller parts
@Agetian @tehdiplomat @kevlahnota one of my ideas was trying to optimize the condition checks done in the triggers, like TriggerSpellAbilityCastOrCopy
And if we could run them in parallel, and break early when one of them returns false?
There is an example of one of my ideas for this, the sleep should symbolize the processing time. If it runs in parallel, it still executes the third one, even it it doesn't need it anymore :/
/* Online Java Compiler and Editor */
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import java.util.function.Supplier;
public class HelloWorld{
public static void main(String []args) throws InterruptedException {
Stream.Builder<Supplier<Boolean>> builder = Stream.builder();
builder.add(() -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("first");
}
catch(InterruptedException e) {}
return true;
});
builder.add(() -> {
try {
TimeUnit.SECONDS.sleep(10);
System.out.println("second");
}
catch(InterruptedException e) {}
return false;
});
builder.add(() -> {
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("third");
}
catch(InterruptedException e) {}
return false;
});
Stream<Supplier<Boolean>> stream = builder.build();
//System.out.format("count: %d", stream.parallel().count());
//System.out.format("allMatch: %s", stream.allMatch(s -> s.get()) ? "true" : "false");
System.out.format("allMatch: %s", stream.parallel().allMatch(s -> s.get()) ? "true" : "false");
}
}
Sounds like a good approach. Forge has lots of bad architectural decisions that would need to be split to be able to utilize improved threading in these areas..
On my local copy, when using threads or any parallel computation that involves shuffling or reversing instance of FCollection, it will crash.. I use this helper class
package forge.util;
import forge.util.collect.FCollection;
import java.util.*;
public class CollectionUtil {
public static <T> void shuffle(List<T> list) {
shuffle(list, MyRandom.getRandom());
}
public static <T> void shuffle(List<T> list, Random random) {
if (list instanceof FCollection) {
//long start = System.nanoTime();
shuffleList(list, random);
//long finish = System.nanoTime();
//System.out.println("Time nano: " + (finish - start));
} else {
Collections.shuffle(list, random);
}
}
public static <T> void shuffleList(List<T> a, Random r) {
int n = a.size();
for (int i = 0; i < n; i++) {
int change = i + r.nextInt(n - i);
swap(a, i, change);
}
}
private static <T> void swap(List<T> a, int i, int change) {
T helper = a.get(i);
a.set(i, a.get(change));
a.set(change, helper);
}
public static <T> void reverse(List<T> list) {
if (list == null || list.isEmpty())
return;
if (list instanceof FCollection) {
//long start = System.nanoTime();
reverseWithRecursion(list, 0, list.size() - 1);
//long finish = System.nanoTime();
//System.out.println("Time nano: " + (finish - start));
} else {
Collections.reverse(list);
}
}
public static <T> void reverseWithRecursion(List<T> list) {
if (list.size() > 1) {
T value = list.remove(0);
reverseWithRecursion(list);
list.add(value);
}
}
public static <T> void reverseWithRecursion(List<T> list, int startIndex, int lastIndex) {
if (startIndex < lastIndex) {
T t = list.get(lastIndex);
list.set(lastIndex, list.get(startIndex));
list.set(startIndex, t);
startIndex++;
lastIndex--;
reverseWithRecursion(list, startIndex, lastIndex);
}
}
}
and replace all Collection,shuffle(..) and Collection.reverse(...) to use CollectionUtil.shuffle(...) and CollectionUtil.reverse(...)
@kevlahnota my first try will be using Threads on cases where the Main list isn't altered, like Filtering.
For Loops that shouldn't alter object, but only filter them should try to be optimized using Threading
In the Future, that might help to check which creatures should attack or block better.
We also need to see if we should do more Threading on Desktop than on Mobile?