Skip to content

Commit e1285ee

Browse files
committed
Fix the Unity job handling not calling the completed callback
1 parent a60617b commit e1285ee

2 files changed

Lines changed: 61 additions & 43 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* Savestates now restore better file descriptors
1111
* Fix SDL getting window dimensions of zero
1212
* Better handling of opendir/readdir and savefiles
13+
* Fix the Unity job handling not calling the completed callback
1314

1415
## [1.4.7] - 2025-10-06
1516
### Added

src/library/UnityHacks.cpp

Lines changed: 60 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,14 @@ typedef void ujob_job_t;
7979
typedef long ujob_handle_t;
8080
typedef void ujob_dependency_chain;
8181
typedef int WorkStealingRange;
82-
typedef void* (*JobsCallbackFunctions)(void*, int);
82+
83+
struct JobsCallbackFunctions {
84+
void (*execute)(void*, int);
85+
void (*completed)(void*);
86+
};
87+
88+
// typedef void (*JobsCallbackFunctions)(void*);
89+
// typedef void (*JobsCallbackFunctionsParallel)(void*, int);
8390
typedef void ScriptingBackendNativeObjectPtrOpaque;
8491
class JobScheduleParameters;
8592
class JobFence;
@@ -681,15 +688,33 @@ static unsigned long U6_ujob_schedule_job_internal(ujob_control_t* x, ujob_handl
681688
}
682689

683690
struct callback_args_t {
684-
JobsCallbackFunctions* func;
691+
int count;
692+
JobsCallbackFunctions funcs;
685693
void* arg;
686-
int loop_index;
694+
// std::mutex mutex;
695+
// std::condition_variable cv;
696+
// bool completed;
687697
};
688698

689-
static void* job_callback(void* arg, int)
699+
static void job_callback_execute(void* arg, int)
700+
{
701+
callback_args_t* args = reinterpret_cast<callback_args_t*>(arg);
702+
703+
/* Perform all iterations of the loop inside this job */
704+
for (int i = 0; i < args->count; i++)
705+
args->funcs.execute(args->arg, i);
706+
}
707+
708+
static void job_callback_completed(void* arg)
690709
{
691710
callback_args_t* args = reinterpret_cast<callback_args_t*>(arg);
692-
return (*args->func)(args->arg, args->loop_index);
711+
if (args->funcs.completed) {
712+
args->funcs.completed(args->arg);
713+
}
714+
715+
// std::unique_lock<std::mutex> lock(args->mutex);
716+
// args->completed = true;
717+
// args->cv.notify_all();
693718
}
694719

695720
/* The function parameters from the symbol are *wrong*! 6th parameter must be
@@ -703,50 +728,42 @@ static void* job_callback(void* arg, int)
703728
*/
704729
static ujob_handle_t U6_ujob_schedule_parallel_for_internal(ujob_control_t* x, JobsCallbackFunctions* y, void* job_callback_arg, WorkStealingRange* a, unsigned int count, unsigned long c, ujob_handle_t const* d, long e)
705730
{
706-
LOG(LL_TRACE, LCF_HACKS, "U6_ujob_schedule_parallel_for_internal called with callback %p, steal mode %d, unknown uint %d", *y, a?(*a):0, count);
731+
LOG(LL_TRACE, LCF_HACKS, "U6_ujob_schedule_parallel_for_internal called with callback args %p , steal mode %d, count %d, ujob_handle_t %p", job_callback_arg, a?(*a):0, count, d);
707732

708733
if (!(Global::shared_config.game_specific_sync & SharedConfig::GC_SYNC_UNITY_JOBS))
709734
return orig::U6_ujob_schedule_parallel_for_internal(x, y, job_callback_arg, a, count, c, d, e);
710735

711736
ujob_handle_t ret = 0;
712-
static JobsCallbackFunctions loop_callback = &job_callback;
737+
static JobsCallbackFunctions loop_callbacks = {&job_callback_execute, job_callback_completed};
738+
/* Instead of scheduling all the jobs in one call, we schedule one
739+
* individual job that will perform all the iterations in order. The job may still
740+
* run on a worker thread, but it should be fine for determinism.
741+
* Normally, the job callback function is receiving the loop index as
742+
* second argument, so we pass our own callback function, which receives
743+
* the original callback, the original callback argument, and the iteration
744+
* count. */
745+
callback_args_t* args = new callback_args_t;
746+
args->count = count;
747+
args->funcs.execute = y->execute;
748+
args->funcs.completed = y->completed;
749+
args->arg = job_callback_arg;
750+
// args->completed = false;
751+
752+
// std::unique_lock<std::mutex> lock(args->mutex);
753+
754+
ret = orig::U6_ujob_schedule_parallel_for_internal(x, &loop_callbacks, args, a, 1, c, d, e);
713755

714-
if (count == 1) {
715-
ret = orig::U6_ujob_schedule_parallel_for_internal(x, y, job_callback_arg, a, count, c, d, e);
716-
717-
/* In newer Unity 6 versions, there is a dedicated internal function for
718-
* waiting on a job */
719-
if (orig::U6_ujob_wait_for)
720-
orig::U6_ujob_wait_for(x, ret, 1);
721-
else if (orig::U2K_JobQueue_WaitForJobGroupID)
722-
orig::U2K_JobQueue_WaitForJobGroupID(reinterpret_cast<JobQueue*>(x), reinterpret_cast<JobGroup*>(ret), 0, true);
723-
}
724-
else {
725-
/* Instead of scheduling all the jobs in one call, we schedule each
726-
* individual job and wait for the job to complete. The job may still
727-
* run on a worker thread, but it should be fine for determinism.
728-
* Normally, the job callback function is receiving the loop index as
729-
* second argument, so we pass our own callback function, which receives
730-
* the original callback, the original callback argument, and the loop
731-
* index.
732-
*/
733-
for (int i=0; i < count; i++) {
734-
callback_args_t* args = new callback_args_t;
735-
args->func = y;
736-
args->arg = job_callback_arg;
737-
args->loop_index = i;
738-
739-
ret = orig::U6_ujob_schedule_parallel_for_internal(x, &loop_callback, args, a, 1, c, d, e);
740-
741-
if (orig::U6_ujob_wait_for)
742-
orig::U6_ujob_wait_for(x, ret, 1);
743-
else if (orig::U2K_JobQueue_WaitForJobGroupID)
744-
orig::U2K_JobQueue_WaitForJobGroupID(reinterpret_cast<JobQueue*>(x), reinterpret_cast<JobGroup*>(ret), 0, true);
745-
746-
/* It should be safe to delete our custom callback argument here */
747-
delete args;
748-
}
749-
}
756+
/* Manually waiting on all jobs to execute */
757+
// args->cv.wait(lock, [&args] { return args->completed; });
758+
759+
if (orig::U6_ujob_wait_for)
760+
orig::U6_ujob_wait_for(x, ret, 1);
761+
else if (orig::U2K_JobQueue_WaitForJobGroupID)
762+
orig::U2K_JobQueue_WaitForJobGroupID(reinterpret_cast<JobQueue*>(x), reinterpret_cast<JobGroup*>(ret), 0, true);
763+
764+
/* It should be safe to delete our custom callback argument here */
765+
// delete args->loop_index;
766+
delete args;
750767

751768
return ret;
752769
}

0 commit comments

Comments
 (0)