многопоточный интелект для своего моба?

Версия Minecraft
1.7.10
235
3
21
вечер добрый
"не лезь, оно тебя сожрет" - лучшее описание для этого вопроса, но все же
как работают потоки я хорошо знаю, меня интерисует, стоит ли вообще за это садится и с чем возможно мне предстоит столкнуться
у меня есть свой зомби который ломает блоки (это самое тяжелое) и еще штуки 2-3 новых ai, и все бы ничего, но для моего режима в порядке вещей, что в округе по 500+ зомби
от такого количества однопоточный кубач начинает вешаться
возможно ли вынести обработку хотя бы алгоритма ломания блоков в отдельный поток(и), подцепляется алгоритм в onUpdate() у моего моба, на вход по сути все что ему нужно это координаты игрока которого он атакует, свои координаты, и возможность получать информацию о блоках по координатам
на выходе отправка пакетов с частицами и звуками (не думаю что тут что нибудь сломается от нескольких потоков, а если и сломается сделать единый поток для отправки пакетов не сложно) и финальное ломание блока (происходит не так часто, проще всего наверное будет оставить в основном потоке, чтобы не сломать ничего)
что может тут сломаться? не будет ли приколов, если 2 потока сразу будут пытаться получить 1 блок по координатам? тот же вопрос про чтение полей entity и получения списка сущностей в радиусе (для других алгоритмов)?
 

tox1cozZ

aka Agravaine
8,455
598
2,892
Встраивать многопоточность в готовое однопоточное приложение - это крайне сложно. Одно дело, когда эти потоки независимы друг от друга. Другое дело, когда нужно это все между собой правильно синхронизировать.
Довольно старая статья, возможно что-то подчерпнешь для себя - Как не нужно писать большие сервера
 
7,099
324
1,510
с чем возможно мне предстоит столкнуться
Если сделаешь изменяемое состояние и будет работать из нескольких потоков, то в первую очередь будет недетерминизм

блок по координатам
чтение полей entity и получения списка сущностей в радиусе
Это изменяемое состояние

Можно посмотреть в сторону Akarin - ядро для ванили, разработчики распараллелили в нем обработку сущностей, но как они это сделали я не знаю
 
7,099
324
1,510
235
3
21
Встраивать многопоточность в готовое однопоточное приложение - это крайне сложно. Одно дело, когда эти потоки независимы друг от друга. Другое дело, когда нужно это все между собой правильно синхронизировать.
Довольно старая статья, возможно что-то подчерпнешь для себя - Как не нужно писать большие сервера
cтатья интересная, но я не потяну переписать майн и forge вместе с ним под такую штуку, а если и смогу, то на этой уйдет года 2 с учетом того, что я забью на учебу, диплом и работу
 

tox1cozZ

aka Agravaine
8,455
598
2,892
В том то и дело, что вынести какуе-то часть(мобы, игроки, тайлы) в потоки и не поломав другие довольно сложно на мой взгляд.
Если тут есть виртуозы в многопоточном программирование - растолкуйте, пожалуйста)
 
7,099
324
1,510
Гуру Потоков
Очень легко распараллеливаются чистые функции.
Пример:
Есть какая-то строка, которая содержит подстроку, начинающуюся на ( и оканчивающуюся на )
"abc(def)ijk"
"(ebc)a"
Следующая функция решает эту задачу
Java:
String subkek(String v){
    int first = v.indexOf("(");
    int second = v.lastIndexOf(")");
    return v.substring(first+1,second);
}
Тут стоит обратить внимание, что вычисление first не связано зависимостями с вычислением second, поэтому эти два вычисления можно производить асинхронно(например начать одновременно).
Зависимостей нет потому что indexOf и lastIndexOf не изменяют какое-либо состояние и не производят побочных эффектов.

Если сможешь построить алгоритм ИИ моба так, что большая его часть будет чистыми функциями, то уже будет легко распараллелить без нарушения семантики
 
235
3
21
классно я написал пул через u, ну да ладно
начал накидывать структуру и понял, что есть плюс, с полями в своем мобе я могу сделать потокобезопасную работу и спокойно их трогать из нескольких потоков
 
7,099
324
1,510
их трогать из нескольких потоков
Попробуй. Для некоторых задач недетерминизм не критичен. Типо, если два моба пытаются ломать один блок, то не страшно, если один из них сломает раньше, а второй будет еще один тик пытаться учитывать отсутствующий блок
 
235
3
21
начал делать, получается такая структура
есть пул который хранит в себе объекты потоков, очередь в которую из onLivingUpdate() в моем мобе кидаются задачи, пока что создал 1 поток просто для теста
Java:
public class EntityStrongZombieAIThreadPool
{
    public static EntityStrongZombieAIThreadPool pool = new EntityStrongZombieAIThreadPool();
   
    private final int maxThreadCount = 3;
    private volatile int threadCount = 0;
    private List<EntityStrongZombieAIThread> threads = new CopyOnWriteArrayList<>();
    private BlockingQueue<EntityStrongZombieAIThreadTask> tasks = new LinkedBlockingQueue<>();
   
    private EntityStrongZombieAIThreadPool()
    {
        addThread();
    }
   
    public void addThread()
    {
        threads.add(new EntityStrongZombieAIThread(threadCount));
        threadCount++;
        threads.get(0).start();
    }
   
    public void addTask(EntityStrongZombieEngine zombie)
    {
        tasks.add(new EntityStrongZombieAIThreadTask(zombie));
        //System.out.println("New task, in queue - " + this.tasks.size());
    }
   
    public EntityStrongZombieAIThreadTask getTask()
    {
        return tasks.poll();
    }
}
есть поток который пытается забрать из очереди задачу выполнить ее, а после брать снова, пока очередь не станет пустой, как только она пустая, делает паузу в 1ms, чтобы не делать бесконечные проверки, но тут то мне и не нравится момент, за эту милисекунду он бы успел выполнить не 1 десяток, а может быть и сотен задач, что не выгодно, но если убрать то когда очередь будет пуста, цикл просто забьет одно ядро процессора на 100%, есть у кого идеи?
Java:
public class EntityStrongZombieAIThread extends Thread
{
    public final int threadId;
   
    public EntityStrongZombieAIThread(int threadId)
    {
        super();
        this.threadId = threadId;
        this.setName("EntityStrongZombieAIThread-" + this.threadId);
    }
   
    @Override
    public void run()
    {
        EntityStrongZombieAIThreadTask task = null;
       
        while(true)
        {
            while((task = EntityStrongZombieAIThreadPool.pool.getTask()) != null)
            {
                task.run();
                //System.out.println(this.getName() + " has completed task");
            }
           
            try {
                Thread.sleep(1);
                //System.out.println("thread sleeped 1ms");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
уже предвижу что придется делать систему контроля успеваемости выполнения, если потоки перестанут справляться
ну и вообще хочу услышать другие мнения по поводу общей архитектуры, ибо крупно с чужими многопоточными системами не сталкивался
 
235
3
21
дубль 2

есть контролер с пулом и счетчиком задач (не смог найти адекватный способ достать колво объектов в очередях пула), при добавлении задачи увеличивает его
Java:
public class PoolController
{
    public static PoolController instance = new PoolController();
   
    private ExecutorService pool;
    private volatile int tasksCounter = 0;
   
    private PoolController()
    {
        this.pool = Executors.newWorkStealingPool(3);
    }
   
    public void addTask(EntityStrongZombieEngine zombie)
    {
        this.tasksCounter++;
        pool.submit(new BlockBreakingTask(zombie));
    }
   
    public void finishTask()
    {
        this.tasksCounter--;
    }
   
    public int getTasksCounter()
    {
        return this.tasksCounter;
    }
}
есть таск для пула, по сути принимает объект моего моба и просто вызывает выполнения алгоритма прописанного в нем, по завершению отправляет сигнал на счетчик, ну и будет отправлять (если блок успешно сломан) данные в другую коллекцию, обработка которой будет подхватываться в главном потоке, чтобы не произошло сюрпризов при асинхронном ломании блоков
Java:
public class BlockBreakingTask implements Runnable
{
    private EntityStrongZombieEngine zombie;
    private Random rand;
   
    public BlockBreakingTask(EntityStrongZombieEngine zombie)
    {
        this.zombie = zombie;
        this.rand = new Random();
    }
   
    @Override
    public void run()
    {
        this.updateBlockDamage();
        //тут если блок успешно сломан будут отправляться данные в коллекцию, которая будет обрабатываться в основном потоке
        PoolController.instance.finishTask();  
    }
   
    /*
    *
    * 500+ строк алгоритма
    *
    */
}
ивент для синхронизации потоков, тут проверяется размер очереди, если она не справляется, то задачи поступать не будут пока, она не будет пустая, но будет уведомление о кол-ве пропущенных тиков, тут же потом расположится коллекция с результатами алгоритма (какие блоки нужно сломать)
Java:
public class SyncronizeEvent
{
    public static boolean skipNextTick = false;
    private int skipedTicks = 0;

    @SubscribeEvent
    public void onWorldTick(TickEvent.WorldTickEvent e)
    {
        if(!e.world.isRemote && e.phase == Phase.START)
        {
            if(!skipNextTick)
            {
                if(this.skipedTicks > 0)
                {
                    System.out.println("Pool has skiped " + this.skipedTicks + " tick(s)!");
                    this.skipedTicks = 0;
                }
               
                int count = PoolController.instance.getTasksCounter();
                System.out.println("Tasks in pool: " + count);
               
                if(count > 1000)
                {
                    skipNextTick = true;
                }
            }
            else
            {
                skipedTicks++;
                if(PoolController.instance.getTasksCounter() < 1000)
                {
                    skipNextTick = false;
                }
            }
        }
    }
}
ну и закидываются задачи в очередь из onLivingUpdate() в моем мобе, тут также у меня реализована возможноть делать обработку реже по тикам, но эффект (скорость ломания) в тик будет выше
Java:
public void onLivingUpdate()
        if(!this.worldObj.isRemote)
        { 
            if(this.blockBreakingAIDelay > 0)
            {
                this.blockBreakingAIDelay--;
            }
            else
            {
                if(!SyncronizeEvent.skipNextTick)
                {
                    PoolController.instance.addTask(this);
                    this.blockBreakingAIDelay += ModConfig.blockBreakingAIDelay;
                }
            }
        }
}
суть все та же, что думаете, мжб что-то можно допилить еще?
 
Сверху