Skip to content

Instantly share code, notes, and snippets.

@naphipps
Last active June 25, 2024 19:17
Show Gist options
  • Save naphipps/08b4b1cd304141bce205a1aaf7e11a40 to your computer and use it in GitHub Desktop.
Save naphipps/08b4b1cd304141bce205a1aaf7e11a40 to your computer and use it in GitHub Desktop.
Example of how to hook up to box2d V3-alpha task API
/*
I used this in sample.cpp/hpp of the box2d V3-alpha project.
This is just an example of a non-enkiTS job system used with box2d V3-alpha
- Nathan Phipps 1/14/24
*/
// --------- begin of inside sample.cpp ---------
struct NpeSubTaskPayload
{
b2TaskCallback* task = nullptr;
int32_t begin = 0;
int32_t end = 0;
void* taskContext = nullptr;
void* userContext = nullptr;
};
static void NpeExecuteSubTask(np::mem::Delegate& d)
{
NpeSubTaskPayload* payload = static_cast<NpeSubTaskPayload*>(d.GetPayload());
payload->task(payload->begin, payload->end, d.GetId(), payload->taskContext);
Sample* sample = static_cast<Sample*>(payload->userContext);
::np::mem::Destroy(sample->_npe_allocator, payload);
}
static void* NpeEnqueueTask(b2TaskCallback* task, int32_t itemCount, int32_t minRange, void* taskContext, void* userContext)
{
using namespace np; //please ignore, just for clarity and convenience
Sample* sample = static_cast<Sample*>(userContext);
jsys::JobSystem& job_sys = sample->_npe_job_system;
mem::sptr<jsys::Job> completion = job_sys.CreateJob();
int sub_job_count = (int)::std::ceil((float)itemCount / (float)minRange);
// you might want to plan for N-jobs per worker:
int job_count_per_worker = 3;
int threshold = job_count_per_worker * job_sys.GetJobWokerCount();
if (sub_job_count > threshold)
{
sub_job_count = threshold;
// note that this could disrupt the distribution of items per job,
// so minRange may need to be adjusted:
minRange = itemCount / sub_job_count;
}
// You will need to play-test this to figure out the best balance for your target platform!
for (int i=0; i<sub_job_count; ++i)
{
NpeSubTaskPayload* payload = mem::Create<NpeSubTaskPayload>(sample->_npe_allocator, NpeSubTaskPayload{
task,
i * minRange,
i == sub_job_count - 1 ? itemCount : (i + 1) * minRange,
// ^ make sure last sub-task-payload's range includes all remaining items - there are easier/faster ways to do this
taskContext,
userContext
});
mem::sptr<jsys::Job> sub_task_job = job_sys.CreateJob();
sub_task_job->SetPayload(payload);
sub_task_job->SetCallback(NpeExecuteSubTask);
jsys::Job::AddDependency(completion, sub_task_job);
job_sys.SubmitJob(jsys::JobPriority::Normal, sub_task_job);
}
void* completion_ptr = mem::Create<mem::sptr<jsys::Job>>(sample->_npe_allocator, completion);
job_sys.SubmitJob(jsys::JobPriority::Normal, completion);
return completion_ptr;
// If we want NpeFinishTask to be an empty function,
// or if we do not want to allocate that completion_ptr,
// or if we want this function to be synchronous,
// then replace the above 3 lines with:
/*
while (!completion->IsComplete())
thr::ThisThread::yield();
return nullptr;
// If you use the this, NpeFinishTask needs to be an empty function!
*/
}
static void NpeFinishTask(void* taskPtr, void* userContext)
{
using namespace np; //please ignore, just for clarity and convenience
mem::sptr<jsys::Job> completion = *static_cast<mem::sptr<jsys::Job>*>(taskPtr);
// completion job was made dependent on all sub-task jobs
// so we can proceed when the completion job is complete
while (!completion->IsComplete())
thr::ThisThread::yield();
Sample* sample = static_cast<Sample*>(userContext);
mem::Destroy(sample->_npe_allocator, static_cast<mem::sptr<jsys::Job>*>(taskPtr));
}
// ... other stuff from sample.cpp here ...
Sample::Sample(const Settings& settings)
{
// ... other Sample() stuff ...
b2WorldDef worldDef = b2_defaultWorldDef;
worldDef.workerCount = settings.workerCount;
worldDef.enqueueTask = &NpeEnqueueTask;
worldDef.finishTask = &NpeFinishTask;
// ... other Sample() stuff ...
_npe_job_system.SetJobWorkerCount(worldDef.workerCount);
_npe_job_system.Start();
}
Sample::~Sample()
{
_npe_job_system.Stop();
// ... other ~Sample() stuff ...
}
// --------- end of inside sample.cpp ---------
// --------- begin of inside sample.hpp ---------
#include <NP-Engine/NP-Engine.hpp>
class Sample
{
public:
// ... other Sample stuff ...
::np::mem::TraitAllocator _npe_allocator; //really should not be public, alas this is an example
::np::jsys::JobSystem _npe_job_system; //really should not be public, alas this is an example
};
// --------- end of inside sample.hpp ---------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment