Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert simple uses of WorkerThreadPool to parallel for_range() #79490

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/object/worker_thread_pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ class WorkerThreadPool : public Object {

template <typename F>
static _FORCE_INLINE_ void for_range(int i_begin, int i_end, bool parallel, String name, F f) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR, but now I've realized the naming convention of arguments code be a bit mroe strongly followed here...

if (i_begin >= i_end) {
return;
}

if (!parallel) {
for (int i = i_begin; i < i_end; i++) {
f(i);
Expand Down
40 changes: 9 additions & 31 deletions modules/cvtt/image_compress_cvtt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,6 @@ struct CVTTCompressionRowTask {
int height = 0;
};

struct CVTTCompressionJobQueue {
CVTTCompressionJobParams job_params;
const CVTTCompressionRowTask *job_tasks = nullptr;
uint32_t num_tasks = 0;
SafeNumeric<uint32_t> current_task;
};

static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const CVTTCompressionRowTask &p_row_task) {
const uint8_t *in_bytes = p_row_task.in_mm_bytes;
uint8_t *out_bytes = p_row_task.out_mm_bytes;
Expand Down Expand Up @@ -129,18 +122,6 @@ static void _digest_row_task(const CVTTCompressionJobParams &p_job_params, const
}
}

static void _digest_job_queue(void *p_job_queue, uint32_t p_index) {
CVTTCompressionJobQueue *job_queue = static_cast<CVTTCompressionJobQueue *>(p_job_queue);
uint32_t num_tasks = job_queue->num_tasks;
uint32_t total_threads = WorkerThreadPool::get_singleton()->get_thread_count();
uint32_t start = p_index * num_tasks / total_threads;
uint32_t end = (p_index + 1 == total_threads) ? num_tasks : ((p_index + 1) * num_tasks / total_threads);

for (uint32_t i = start; i < end; i++) {
_digest_row_task(job_queue->job_params, job_queue->job_tasks[i]);
}
}

void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {
if (p_image->get_format() >= Image::FORMAT_BPTC_RGBA) {
return; //do not compress, already compressed
Expand Down Expand Up @@ -199,12 +180,12 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {

int dst_ofs = 0;

CVTTCompressionJobQueue job_queue;
job_queue.job_params.is_hdr = is_hdr;
job_queue.job_params.is_signed = is_signed;
job_queue.job_params.options = options;
job_queue.job_params.bytes_per_pixel = is_hdr ? 6 : 4;
cvtt::Kernels::ConfigureBC7EncodingPlanFromQuality(job_queue.job_params.bc7_plan, 5);
CVTTCompressionJobParams job_params;
job_params.is_hdr = is_hdr;
job_params.is_signed = is_signed;
job_params.options = options;
job_params.bytes_per_pixel = is_hdr ? 6 : 4;
cvtt::Kernels::ConfigureBC7EncodingPlanFromQuality(job_params.bc7_plan, 5);

// Amdahl's law (Wikipedia)
// If a program needs 20 hours to complete using a single thread, but a one-hour portion of the program cannot be parallelized,
Expand Down Expand Up @@ -242,12 +223,9 @@ void image_compress_cvtt(Image *p_image, Image::UsedChannels p_channels) {
h = MAX(h / 2, 1);
}

const CVTTCompressionRowTask *tasks_rb = tasks.ptr();

job_queue.job_tasks = &tasks_rb[0];
job_queue.num_tasks = static_cast<uint32_t>(tasks.size());
WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&_digest_job_queue, &job_queue, WorkerThreadPool::get_singleton()->get_thread_count(), -1, true, SNAME("CVTT Compress"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
for_range(0, tasks.size(), true, SNAME("CVTT Compress"), [&](const int i) {
_digest_row_task(job_params, tasks[i]);
});

p_image->set_data(p_image->get_width(), p_image->get_height(), p_image->has_mipmaps(), target_format, data);
}
Expand Down
57 changes: 16 additions & 41 deletions modules/navigation/nav_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1173,53 +1173,28 @@ void NavMap::_update_rvo_simulation() {
}
}

void NavMap::compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent) {
(*(agent + index))->get_rvo_agent_2d()->computeNeighbors(&rvo_simulation_2d);
(*(agent + index))->get_rvo_agent_2d()->computeNewVelocity(&rvo_simulation_2d);
(*(agent + index))->get_rvo_agent_2d()->update(&rvo_simulation_2d);
(*(agent + index))->update();
}

void NavMap::compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent) {
(*(agent + index))->get_rvo_agent_3d()->computeNeighbors(&rvo_simulation_3d);
(*(agent + index))->get_rvo_agent_3d()->computeNewVelocity(&rvo_simulation_3d);
(*(agent + index))->get_rvo_agent_3d()->update(&rvo_simulation_3d);
(*(agent + index))->update();
}

void NavMap::step(real_t p_deltatime) {
deltatime = p_deltatime;

rvo_simulation_2d.setTimeStep(float(deltatime));
rvo_simulation_3d.setTimeStep(float(deltatime));

if (active_2d_avoidance_agents.size() > 0) {
if (use_threads && avoidance_use_multiple_threads) {
WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &NavMap::compute_single_avoidance_step_2d, active_2d_avoidance_agents.ptr(), active_2d_avoidance_agents.size(), -1, true, SNAME("RVOAvoidanceAgents2D"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
} else {
for (NavAgent *agent : active_2d_avoidance_agents) {
agent->get_rvo_agent_2d()->computeNeighbors(&rvo_simulation_2d);
agent->get_rvo_agent_2d()->computeNewVelocity(&rvo_simulation_2d);
agent->get_rvo_agent_2d()->update(&rvo_simulation_2d);
agent->update();
}
}
}

if (active_3d_avoidance_agents.size() > 0) {
if (use_threads && avoidance_use_multiple_threads) {
WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &NavMap::compute_single_avoidance_step_3d, active_3d_avoidance_agents.ptr(), active_3d_avoidance_agents.size(), -1, true, SNAME("RVOAvoidanceAgents3D"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
} else {
for (NavAgent *agent : active_3d_avoidance_agents) {
agent->get_rvo_agent_3d()->computeNeighbors(&rvo_simulation_3d);
agent->get_rvo_agent_3d()->computeNewVelocity(&rvo_simulation_3d);
agent->get_rvo_agent_3d()->update(&rvo_simulation_3d);
agent->update();
}
}
}
bool use_parallel_for = use_threads && avoidance_use_multiple_threads;

for_range(0, active_2d_avoidance_agents.size(), use_parallel_for, SNAME("RVOAvoidanceAgents2D"), [&](const int i) {
NavAgent *agent = active_2d_avoidance_agents[i];
agent->get_rvo_agent_2d()->computeNeighbors(&rvo_simulation_2d);
agent->get_rvo_agent_2d()->computeNewVelocity(&rvo_simulation_2d);
agent->get_rvo_agent_2d()->update(&rvo_simulation_2d);
agent->update();
});
for_range(0, active_3d_avoidance_agents.size(), use_parallel_for, SNAME("RVOAvoidanceAgents3D"), [&](const int i) {
NavAgent *agent = active_3d_avoidance_agents[i];
agent->get_rvo_agent_3d()->computeNeighbors(&rvo_simulation_3d);
agent->get_rvo_agent_3d()->computeNewVelocity(&rvo_simulation_3d);
agent->get_rvo_agent_3d()->update(&rvo_simulation_3d);
agent->update();
});
}

void NavMap::dispatch_callbacks() {
Expand Down
3 changes: 0 additions & 3 deletions modules/navigation/nav_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,9 +207,6 @@ class NavMap : public NavRid {
private:
void compute_single_step(uint32_t index, NavAgent **agent);

void compute_single_avoidance_step_2d(uint32_t index, NavAgent **agent);
void compute_single_avoidance_step_3d(uint32_t index, NavAgent **agent);

void clip_path(const LocalVector<gd::NavigationPoly> &p_navigation_polys, Vector<Vector3> &path, const gd::NavigationPoly *from_poly, const Vector3 &p_to_point, const gd::NavigationPoly *p_to_poly, Vector<int32_t> *r_path_types, TypedArray<RID> *r_path_rids, Vector<int64_t> *r_path_owners) const;
void _update_rvo_simulation();
void _update_rvo_obstacles_tree_2d();
Expand Down
38 changes: 10 additions & 28 deletions modules/text_server_adv/text_server_adv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -883,13 +883,6 @@ class DistancePixelConversion {
}
};

struct MSDFThreadData {
msdfgen::Bitmap<float, 4> *output;
msdfgen::Shape *shape;
msdfgen::Projection *projection;
DistancePixelConversion *distancePixelConversion;
};

static msdfgen::Point2 ft_point2(const FT_Vector &vector) {
return msdfgen::Point2(vector.x / 60.0f, vector.y / 60.0f);
}
Expand Down Expand Up @@ -927,19 +920,6 @@ static int ft_cubic_to(const FT_Vector *control1, const FT_Vector *control2, con
return 0;
}

void TextServerAdvanced::_generateMTSDF_threaded(void *p_td, uint32_t p_y) {
MSDFThreadData *td = static_cast<MSDFThreadData *>(p_td);

msdfgen::ShapeDistanceFinder<msdfgen::OverlappingContourCombiner<msdfgen::MultiAndTrueDistanceSelector>> distanceFinder(*td->shape);
int row = td->shape->inverseYAxis ? td->output->height() - p_y - 1 : p_y;
for (int col = 0; col < td->output->width(); ++col) {
int x = (p_y % 2) ? td->output->width() - col - 1 : col;
msdfgen::Point2 p = td->projection->unproject(msdfgen::Point2(x + .5, p_y + .5));
msdfgen::MultiAndTrueDistance distance = distanceFinder.distance(p);
td->distancePixelConversion->operator()(td->output->operator()(x, row), distance);
}
}

_FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_msdf(FontAdvanced *p_font_data, FontForSizeAdvanced *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *outline, const Vector2 &advance) const {
msdfgen::Shape shape;

Expand Down Expand Up @@ -998,14 +978,16 @@ _FORCE_INLINE_ TextServerAdvanced::FontGlyph TextServerAdvanced::rasterize_msdf(
msdfgen::Projection projection(msdfgen::Vector2(1.0, 1.0), msdfgen::Vector2(-bounds.l, -bounds.b));
msdfgen::MSDFGeneratorConfig config(true, msdfgen::ErrorCorrectionConfig());

MSDFThreadData td;
td.output = &image;
td.shape = &shape;
td.projection = &projection;
td.distancePixelConversion = &distancePixelConversion;

WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&TextServerAdvanced::_generateMTSDF_threaded, &td, h, -1, true, String("FontServerRasterizeMSDF"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
for_range(0, h, true, SNAME("FontServerRasterizeMSDF"), [&](const int p_y) {
msdfgen::ShapeDistanceFinder<msdfgen::OverlappingContourCombiner<msdfgen::MultiAndTrueDistanceSelector>> distanceFinder(shape);
int row = shape.inverseYAxis ? image.height() - p_y - 1 : p_y;
for (int col = 0; col < image.width(); ++col) {
int x = (p_y % 2) ? image.width() - col - 1 : col;
msdfgen::Point2 p = projection.unproject(msdfgen::Point2(x + .5, p_y + .5));
msdfgen::MultiAndTrueDistance distance = distanceFinder.distance(p);
distancePixelConversion.operator()(image.operator()(x, row), distance);
}
});

msdfgen::msdfErrorCorrection(image, shape, projection, p_pixel_range, config);

Expand Down
1 change: 0 additions & 1 deletion modules/text_server_adv/text_server_adv.h
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,6 @@ class TextServerAdvanced : public TextServerExtension {
_FORCE_INLINE_ bool _ensure_glyph(FontAdvanced *p_font_data, const Vector2i &p_size, int32_t p_glyph) const;
_FORCE_INLINE_ bool _ensure_cache_for_size(FontAdvanced *p_font_data, const Vector2i &p_size) const;
_FORCE_INLINE_ void _font_clear_cache(FontAdvanced *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);

_FORCE_INLINE_ Vector2i _get_size(const FontAdvanced *p_font_data, int p_size) const {
if (p_font_data->msdf) {
Expand Down
38 changes: 10 additions & 28 deletions modules/text_server_fb/text_server_fb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,6 @@ class DistancePixelConversion {
}
};

struct MSDFThreadData {
msdfgen::Bitmap<float, 4> *output;
msdfgen::Shape *shape;
msdfgen::Projection *projection;
DistancePixelConversion *distancePixelConversion;
};

static msdfgen::Point2 ft_point2(const FT_Vector &vector) {
return msdfgen::Point2(vector.x / 60.0f, vector.y / 60.0f);
}
Expand Down Expand Up @@ -365,19 +358,6 @@ static int ft_cubic_to(const FT_Vector *control1, const FT_Vector *control2, con
return 0;
}

void TextServerFallback::_generateMTSDF_threaded(void *p_td, uint32_t p_y) {
MSDFThreadData *td = static_cast<MSDFThreadData *>(p_td);

msdfgen::ShapeDistanceFinder<msdfgen::OverlappingContourCombiner<msdfgen::MultiAndTrueDistanceSelector>> distanceFinder(*td->shape);
int row = td->shape->inverseYAxis ? td->output->height() - p_y - 1 : p_y;
for (int col = 0; col < td->output->width(); ++col) {
int x = (p_y % 2) ? td->output->width() - col - 1 : col;
msdfgen::Point2 p = td->projection->unproject(msdfgen::Point2(x + .5, p_y + .5));
msdfgen::MultiAndTrueDistance distance = distanceFinder.distance(p);
td->distancePixelConversion->operator()(td->output->operator()(x, row), distance);
}
}

_FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_msdf(FontFallback *p_font_data, FontForSizeFallback *p_data, int p_pixel_range, int p_rect_margin, FT_Outline *outline, const Vector2 &advance) const {
msdfgen::Shape shape;

Expand Down Expand Up @@ -436,14 +416,16 @@ _FORCE_INLINE_ TextServerFallback::FontGlyph TextServerFallback::rasterize_msdf(
msdfgen::Projection projection(msdfgen::Vector2(1.0, 1.0), msdfgen::Vector2(-bounds.l, -bounds.b));
msdfgen::MSDFGeneratorConfig config(true, msdfgen::ErrorCorrectionConfig());

MSDFThreadData td;
td.output = &image;
td.shape = &shape;
td.projection = &projection;
td.distancePixelConversion = &distancePixelConversion;

WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_native_group_task(&TextServerFallback::_generateMTSDF_threaded, &td, h, -1, true, String("TextServerFBRenderMSDF"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
for_range(0, h, true, SNAME("TextServerFBRenderMSDF"), [&](const int p_y) {
msdfgen::ShapeDistanceFinder<msdfgen::OverlappingContourCombiner<msdfgen::MultiAndTrueDistanceSelector>> distanceFinder(shape);
int row = shape.inverseYAxis ? image.height() - p_y - 1 : p_y;
for (int col = 0; col < image.width(); ++col) {
int x = (p_y % 2) ? image.width() - col - 1 : col;
msdfgen::Point2 p = projection.unproject(msdfgen::Point2(x + .5, p_y + .5));
msdfgen::MultiAndTrueDistance distance = distanceFinder.distance(p);
distancePixelConversion.operator()(image.operator()(x, row), distance);
}
});

msdfgen::msdfErrorCorrection(image, shape, projection, p_pixel_range, config);

Expand Down
1 change: 0 additions & 1 deletion modules/text_server_fb/text_server_fb.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,6 @@ class TextServerFallback : public TextServerExtension {
_FORCE_INLINE_ bool _ensure_glyph(FontFallback *p_font_data, const Vector2i &p_size, int32_t p_glyph) const;
_FORCE_INLINE_ bool _ensure_cache_for_size(FontFallback *p_font_data, const Vector2i &p_size) const;
_FORCE_INLINE_ void _font_clear_cache(FontFallback *p_font_data);
static void _generateMTSDF_threaded(void *p_td, uint32_t p_y);

_FORCE_INLINE_ Vector2i _get_size(const FontFallback *p_font_data, int p_size) const {
if (p_font_data->msdf) {
Expand Down
20 changes: 8 additions & 12 deletions servers/physics_2d/godot_step_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,6 @@ void GodotStep2D::_populate_island(GodotBody2D *p_body, LocalVector<GodotBody2D
}
}

void GodotStep2D::_setup_constraint(uint32_t p_constraint_index, void *p_userdata) {
GodotConstraint2D *constraint = all_constraints[p_constraint_index];
constraint->setup(delta);
}

void GodotStep2D::_pre_solve_island(LocalVector<GodotConstraint2D *> &p_constraint_island) const {
uint32_t constraint_count = p_constraint_island.size();
uint32_t valid_constraint_count = 0;
Expand All @@ -90,7 +85,7 @@ void GodotStep2D::_pre_solve_island(LocalVector<GodotConstraint2D *> &p_constrai
p_constraint_island.resize(valid_constraint_count);
}

void GodotStep2D::_solve_island(uint32_t p_island_index, void *p_userdata) const {
void GodotStep2D::_solve_island(uint32_t p_island_index) const {
const LocalVector<GodotConstraint2D *> &constraint_island = constraint_islands[p_island_index];

for (int i = 0; i < iterations; i++) {
Expand Down Expand Up @@ -238,10 +233,10 @@ void GodotStep2D::step(GodotSpace2D *p_space, real_t p_delta) {
}

/* SETUP CONSTRAINTS / PROCESS COLLISIONS */

uint32_t total_constraint_count = all_constraints.size();
WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep2D::_setup_constraint, nullptr, total_constraint_count, -1, true, SNAME("Physics2DConstraintSetup"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
for_range(0, all_constraints.size(), true, SNAME("Physics2DConstraintSetup"), [&](const int i) {
GodotConstraint2D *constraint = all_constraints[i];
constraint->setup(delta);
});

{ //profile
profile_endtime = OS::get_singleton()->get_ticks_usec();
Expand All @@ -260,8 +255,9 @@ void GodotStep2D::step(GodotSpace2D *p_space, real_t p_delta) {

// Warning: _solve_island modifies the constraint islands for optimization purpose,
// their content is not reliable after these calls and shouldn't be used anymore.
group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep2D::_solve_island, nullptr, island_count, -1, true, SNAME("Physics2DConstraintSolveIslands"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
for_range(0, island_count, true, SNAME("Physics2DConstraintSolveIslands"), [&](const int i) {
_solve_island(i);
});

{ //profile
profile_endtime = OS::get_singleton()->get_ticks_usec();
Expand Down
3 changes: 1 addition & 2 deletions servers/physics_2d/godot_step_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ class GodotStep2D {
LocalVector<GodotConstraint2D *> all_constraints;

void _populate_island(GodotBody2D *p_body, LocalVector<GodotBody2D *> &p_body_island, LocalVector<GodotConstraint2D *> &p_constraint_island);
void _setup_constraint(uint32_t p_constraint_index, void *p_userdata = nullptr);
void _pre_solve_island(LocalVector<GodotConstraint2D *> &p_constraint_island) const;
void _solve_island(uint32_t p_island_index, void *p_userdata = nullptr) const;
void _solve_island(uint32_t p_island_index) const;
void _check_suspend(LocalVector<GodotBody2D *> &p_body_island) const;

public:
Expand Down
20 changes: 8 additions & 12 deletions servers/physics_3d/godot_step_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,6 @@ void GodotStep3D::_populate_island_soft_body(GodotSoftBody3D *p_soft_body, Local
}
}

void GodotStep3D::_setup_constraint(uint32_t p_constraint_index, void *p_userdata) {
GodotConstraint3D *constraint = all_constraints[p_constraint_index];
constraint->setup(delta);
}

void GodotStep3D::_pre_solve_island(LocalVector<GodotConstraint3D *> &p_constraint_island) const {
uint32_t constraint_count = p_constraint_island.size();
uint32_t valid_constraint_count = 0;
Expand All @@ -130,7 +125,7 @@ void GodotStep3D::_pre_solve_island(LocalVector<GodotConstraint3D *> &p_constrai
p_constraint_island.resize(valid_constraint_count);
}

void GodotStep3D::_solve_island(uint32_t p_island_index, void *p_userdata) {
void GodotStep3D::_solve_island(uint32_t p_island_index) {
LocalVector<GodotConstraint3D *> &constraint_island = constraint_islands[p_island_index];

int current_priority = 1;
Expand Down Expand Up @@ -342,10 +337,10 @@ void GodotStep3D::step(GodotSpace3D *p_space, real_t p_delta) {
}

/* SETUP CONSTRAINTS / PROCESS COLLISIONS */

uint32_t total_constraint_count = all_constraints.size();
WorkerThreadPool::GroupID group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep3D::_setup_constraint, nullptr, total_constraint_count, -1, true, SNAME("Physics3DConstraintSetup"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
for_range(0, all_constraints.size(), true, SNAME("Physics3DConstraintSetup"), [&](const int i) {
GodotConstraint3D *constraint = all_constraints[i];
constraint->setup(delta);
});

{ //profile
profile_endtime = OS::get_singleton()->get_ticks_usec();
Expand All @@ -364,8 +359,9 @@ void GodotStep3D::step(GodotSpace3D *p_space, real_t p_delta) {

// Warning: _solve_island modifies the constraint islands for optimization purpose,
// their content is not reliable after these calls and shouldn't be used anymore.
group_task = WorkerThreadPool::get_singleton()->add_template_group_task(this, &GodotStep3D::_solve_island, nullptr, island_count, -1, true, SNAME("Physics3DConstraintSolveIslands"));
WorkerThreadPool::get_singleton()->wait_for_group_task_completion(group_task);
for_range(0, island_count, true, SNAME("Physics3DConstraintSolveIslands"), [&](const int i) {
_solve_island(i);
});

{ //profile
profile_endtime = OS::get_singleton()->get_ticks_usec();
Expand Down
Loading