EliasFarhan / NekoEngine

Generic 3d engine based on SDL2 and OpenGL ES 3/WebGL2
13 stars 5 forks source link

Stocker et executer un job générique? [Oleg] #18

Closed LoshkinOleg closed 4 years ago

LoshkinOleg commented 4 years ago

J'ai réussi a créer une implémentation d'un Job qui accepte une fonction avec un nombre d'arguments variables, mais j'ai un soucis lorsqu'il s'agit de stocker les Jobs...

Un Job est une classe templatisée de la même manière qu'une std::function (donc Job<ReturnType(Args...)>() ) mais on dois stocker des implémentations sécialisées dans un std::vector...

Ya t'il un moyen de stocker des Jobs génériques puis les executer plus tard?

Voici le code:

template<typename ReturnType, typename... Args> class Job;
template<typename ReturnType, typename... Args> class Job<ReturnType(Args...)>
{
public:
    Job(std::function<ReturnType(Args...)> func, std::tuple<Args...> args): func_(func), args_(args) {};

    ReturnType Execute()
    {
        return std::apply(func_,args_);
    }
private:
    std::function<ReturnType(Args...)> func_;
    std::tuple<Args...> args_;
};

class JobSystem{
public:
    template<typename ReturnType, typename... Args>
    std::future<ReturnType> AddJob(std::function<ReturnType(Args...)> func, std::tuple<Args...> args){
        jobQueue_.emplace_back(Job<ReturnType(Args...)>(func, args));
        return std::promise<ReturnType>().get_future();
    }

    void RunJobs(){
        for (auto& job : jobQueue_){
            job.Execute();
        }
    }
private:
    std::vector<Job> jobQueue_;
};

L'erreur est à la ligne "std::vector jobQueue_;" : Use of template "Job" requires template arguments.

EliasFarhan commented 4 years ago

Tu ne peux pas stocker un type templatisé dans un std::vector, parce que un Job avec certains paramètres template ne sera pas le même type qu'un autre Job templatisé autrement.

Tu devrais donc te concentrer avec un std::function<void(void)> pour ton Job, mais comment vas-tu passer des arguments alors? À travers le lambda capture:

jobSystem.AddJob([this, &neededArgument]{actualFunc(neededArgument);});

Il faut toutefois faire gaffe à ce qui est donné en capture reste en mémoire en quittant le scope, sinon ça crash.

LoshkinOleg commented 4 years ago

Oki, ça marche!

Par contre, du coup ya pas possibilité de récupérer une valeur de retour? Genre le futur?

LoshkinOleg commented 4 years ago

Ok j'ai du mal avec les lambdas:

void Work(std::function<void(void)> (* GrabJob)())
{
    while (true)
    {
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock);

        GrabJob()(); // Grab job and execute
    }
}

class JobSystem
{
public:
    JobSystem()
    {
        for (int i = 0; i < DEFAULT_WORKER_SIZE; ++i)
        {
            workers_[i] = std::thread(Work, [this] { GrabJob(); });
        }
    }

    void AddJob(std::function<void(void)> job)
    {
        jobQueue_.push(job);
    }

    void ExecuteJobs()
    {
        const size_t queueSize = jobQueue_.size();
        for (int i = 0; i < queueSize; ++i)
        {
            cv.notify_one();
        }
    }

    std::function<void(void)> GrabJob()
    {
        auto job = jobQueue_.top();
        jobQueue_.pop();
        return job;
    }

private:
    const static size_t DEFAULT_WORKER_SIZE = 2;
    std::stack<std::function<void(void)>> jobQueue_;
    std::array<std::thread, DEFAULT_WORKER_SIZE> workers_;
};

Donc actuellement, je veux avoir un array de threads qui wait() en boucle jusqu'à ce qu'ils notifiés. La fonction Work() est celle qui tourne en boucle sur un thread. En paramètre je veux lui passer une methode de JobSystem qui sert à récupérer le job suivant dans la job queue, méthode qui retourne une fonction à appeler (le job à executer donc).

Le problème est que ça me sort une erreur cryptique à la ligne

workers_[i] = std::thread(Work, [this] { GrabJob(); });

qui vient d'un template interne à std::thread et je comprends absolument pas l'erreur:

"In instantiation of function template specialization 'std::thread::thread<void (&)(std::function<void ()> ()()), (lambda at /home/user/Desktop/NekoEngine/core/include/engine/jobsystem.h:88:45), void>'. In instantiation of function template specialization 'std::thread::thread<void (&)(std::function<void ()> ()()), (lambda at /home/user/Desktop/NekoEngine/core/include/engine/jobsystem.h:88:45), void>'. "

J'ai l'impression que je deverais vraiment me renseigner sur le fonctionnement des templates dans le C++...

EliasFarhan commented 4 years ago

Tu ne me mets pas le message d'erreur au complet, juste où il y a une erreur...

Je comprends pas cette ligne:

void Work(std::function<void(void)> (* GrabJob)())

Pourquoi pas ça:

void Work(std::function<void(void)> GrabJob)
LoshkinOleg commented 4 years ago

Tu ne me mets pas le message d'erreur au complet, juste où il y a une erreur...

Si justement, c'est toute l'erreur qu'il me sort x) Si on suis l'erreur, ça m'amène à l'implémentation d'un thread avec ce bout de code là:

    template<size_t... _Ind>
      typename __result<_Tuple>::type
      _M_invoke(_Index_tuple<_Ind...>)
      { return std::__invoke(std::get<_Ind>(std::move(_M_t))...); }

Je comprends pas cette ligne: Pourquoi pas ça:

Nope c'est une déclaration d'un ptr de fonction. C'est les trucs qui se déclarent comme ça:

returnType (*funcName)(args)   par ex: int (*sumOfInts)(int,int) = someClass.SumTheseInts;

"std::function<void(void)>" c'est le return type de GrabJob. "(*GrabJob)" c'est le nom local que je donne au ptr de fonction. "()": c'est les arguments de la fonction std::function<void(void)> JobSystem::GrabJob()

Au fait GrabJob va chercher la dernière std::function<void(void)> qui est dans jobQueue et le retourne. Dans Work() je passe un ptr de fonction vers GrabJob() que Work() va utiliser pour récuperer un std::function<void(void)> et l'executer.

LoshkinOleg commented 4 years ago

J'ai refactor un peu le code pour que ce soit plus clair:

using GrabJobFunctionPtr = std::function<void(void)> (*)();

void Work(GrabJobFunctionPtr jobGrabber)
{
    while (true)
    {
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock);

        jobGrabber()(); // Grab job and execute
    }
}

class JobSystem
{
public:
    JobSystem()
    {
        for (int i = 0; i < DEFAULT_WORKER_SIZE; ++i)
        {
            workers_[i] = std::thread(Work, this->GrabJob);
        }
    }

    void AddJob(std::function<void(void)> job)
    {
        jobQueue_.push(job);
    }

    void ExecuteJobs()
    {
        const size_t queueSize = jobQueue_.size();
        for (int i = 0; i < queueSize; ++i)
        {
            cv.notify_one();
        }
    }

    std::function<void(void)> GrabJob()
    {
        auto job = jobQueue_.top();
        jobQueue_.pop();
        return job;
    }

private:
    const static size_t DEFAULT_WORKER_SIZE = 2;
    std::stack<std::function<void(void)>> jobQueue_;
    std::array<std::thread, DEFAULT_WORKER_SIZE> workers_;
};

Là ya toujours l'autre erreur mais c'est au moment de passer GrabJob au thread qu'il y a aussi une erreur sur le paramètre: "Reference to non-static member function must be called; did you mean to call it with no arguments? (fix available)"

EliasFarhan commented 4 years ago

Non, ce n'est pas le message d'erreur complet, il n'y a pas de verbe (je sais que les messages d'erreur sont pas toujours clairs, mais y a un toujours un verbe et une explication), là c'est juste "In file, in file"... Donc sors-moi TOUT le log qui sort de la compilation.

Okay, plutôt que d'utiliser un pointeur de fonction pour GrabJob, tu peux aussi utiliser std::function avec autant d'alias que tu veux:

std::function<std::function<void(void)>(void)>

Et pourquoi t'utilises une stack pour les jobs (tu veux pas utiliser une queue plutôt?) parce que ta stack est LIFO, donc la première tâche envoyée sera exécutée en dernier ce qui ne fait pas de sens

LoshkinOleg commented 4 years ago

SOLUTION:

Utiliser des std::function<void()> pour stocker n'importe quel type de fonction. Définir les instances individuelles de ces fonctions avec des lambdas + capture.

Ex:

void PowTwo(const int arg, int& output)
{
    output = arg * arg;
}

int main()
{
   std::vector<std::function<void()>> functionsVector;
   int result = 2;
   for (size_t i = 0; i < 5; i++)
   {
       functionsVector.push_back( [i, &result] { PowTwo(i, result)}; );
   }

   for (size_t i = 0; i < 5; i++)
   {
       functionsVector[i]();
   }

    return result;
}