Skip to content

Commit 724a0bd

Browse files
committed
Implement asynchronous model loading
1 parent 35de058 commit 724a0bd

File tree

2 files changed

+205
-3
lines changed

2 files changed

+205
-3
lines changed

render_pipeline/rppanda/showbase/loader.hpp

+47
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
#include <audioSound.h>
4242
#include <textFont.h>
4343

44+
#include <unordered_set>
45+
4446
#include <boost/optional.hpp>
4547

4648
#include <render_pipeline/rppanda/showbase/direct_object.hpp>
@@ -54,6 +56,41 @@ class ShowBase;
5456

5557
class RENDER_PIPELINE_DECL Loader : public DirectObject
5658
{
59+
public:
60+
/**
61+
* Returned by loadModel when used asynchronously. This class is
62+
* modelled after Future, and can be awaited.
63+
*/
64+
class RENDER_PIPELINE_DECL Callback
65+
{
66+
public:
67+
Callback(Loader* loader, int num_objects,
68+
const std::function<void(const std::vector<NodePath>&)>& callback);
69+
70+
void got_object(size_t index, NodePath object);
71+
72+
/** Cancels the request. Callback won't be called. */
73+
void cancel();
74+
75+
/** Returns true if the request was cancelled. */
76+
bool cancelled() const;
77+
78+
/** Returns true if all the requests were finished or cancelled. */
79+
bool done() const;
80+
81+
/** Suspending the thread to wait if necessary. */
82+
void wait() const;
83+
84+
private:
85+
friend class Loader;
86+
87+
Loader* loader_;
88+
std::vector<NodePath> objects_;
89+
std::function<void(const std::vector<NodePath>&)> callback_;
90+
std::unordered_set<AsyncTask*> requests_;
91+
std::vector<AsyncTask*> request_list_;
92+
};
93+
5794
public:
5895
Loader(ShowBase& base);
5996
Loader(const Loader&) = delete;
@@ -76,6 +113,16 @@ class RENDER_PIPELINE_DECL Loader : public DirectObject
76113
std::vector<NodePath> load_model(const std::vector<Filename>& model_list, const LoaderOptions& loader_options={},
77114
boost::optional<bool> no_cache=boost::none, bool allow_instance=false, boost::optional<bool> ok_missing=boost::none);
78115

116+
std::shared_ptr<Callback> load_model_async(const Filename& model_path, const LoaderOptions& loader_options={},
117+
boost::optional<bool> no_cache=boost::none, bool allow_instance=false, boost::optional<bool> ok_missing=boost::none,
118+
const std::function<void(const std::vector<NodePath>&)>& callback={},
119+
boost::optional<int> priority=boost::none);
120+
121+
std::shared_ptr<Callback> load_model_async(const std::vector<Filename>& model_list, const LoaderOptions& loader_options={},
122+
boost::optional<bool> no_cache=boost::none, bool allow_instance=false, boost::optional<bool> ok_missing=boost::none,
123+
const std::function<void(const std::vector<NodePath>&)>& callback={},
124+
boost::optional<int> priority = boost::none);
125+
79126
/**
80127
* This loads a special model as a TextFont object, for rendering
81128
* text with a TextNode. A font file must be either a special

src/rppanda/showbase/loader.cpp

+158-3
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,28 @@ class Loader::Impl
6363
void pre_load_model(LoaderOptions& this_options, bool& this_ok_missing,
6464
boost::optional<bool> no_cache, bool allow_instance, boost::optional<bool> ok_missing);
6565

66+
/**
67+
* A model or sound file or some such thing has just been
68+
* loaded asynchronously by the sub-thread. Add it to the list
69+
* of loaded objects, and call the appropriate callback when it's
70+
* time.
71+
*/
72+
void got_async_object(const Event* ev);
73+
6674
public:
75+
static size_t loader_index_;
76+
6777
ShowBase& base_;
6878
::Loader* loader_;
79+
80+
std::string hook_;
81+
std::unordered_map<AsyncTask*, std::pair<std::shared_ptr<Callback>, size_t>> requests_;
6982
};
7083

84+
size_t Loader::Impl::loader_index_ = 0;
85+
7186
Loader::Impl::Impl(ShowBase& base): base_(base)
7287
{
73-
loader_ = ::Loader::get_global_ptr();
7488
}
7589

7690
void Loader::Impl::pre_load_model(LoaderOptions& this_options, bool& this_ok_missing,
@@ -101,19 +115,114 @@ void Loader::Impl::pre_load_model(LoaderOptions& this_options, bool& this_ok_mis
101115
this_options.set_flags(this_options.get_flags() | LoaderOptions::LF_allow_instance);
102116
}
103117

118+
void Loader::Impl::got_async_object(const Event* ev)
119+
{
120+
if (ev->get_num_parameters() != 1)
121+
{
122+
rppanda_showbase_cat.error() << "Invalid number of paramter: " << ev->get_num_parameters() << std::endl;
123+
return;
124+
}
125+
126+
const auto& param = ev->get_parameter(0);
127+
auto request = DCAST(AsyncTask, param.get_typed_ref_count_value());
128+
129+
auto found = requests_.find(request);
130+
if (found == requests_.end())
131+
return;
132+
133+
auto cb = found->second.first; // should hold callback for Callback::get_object
134+
auto i = found->second.second;
135+
136+
if (cb->cancelled() || request->cancelled())
137+
{
138+
// Shouldn't be here.
139+
requests_.erase(request);
140+
return;
141+
}
142+
143+
cb->requests_.erase(request);
144+
if (!cb->requests_.empty())
145+
requests_.erase(request);
146+
147+
PandaNode* result = DCAST(PandaNode, request->get_result());
148+
149+
cb->got_object(i, NodePath(result));
150+
}
151+
152+
// ************************************************************************************************
153+
154+
Loader::Callback::Callback(Loader* loader, int num_objects,
155+
const std::function<void(const std::vector<NodePath>&)>& callback) : loader_(loader), callback_(callback)
156+
{
157+
objects_.resize(num_objects);
158+
}
159+
160+
void Loader::Callback::got_object(size_t index, NodePath object)
161+
{
162+
objects_[index] = object;
163+
164+
if (!done())
165+
return;
166+
167+
loader_ = nullptr;
168+
if (callback_)
169+
callback_(objects_);
170+
}
171+
172+
void Loader::Callback::cancel()
173+
{
174+
if (!loader_)
175+
return;
176+
177+
for (const auto& request : requests_)
178+
{
179+
loader_->impl_->loader_->remove(request);
180+
loader_->impl_->requests_.erase(request);
181+
}
182+
183+
loader_ = nullptr;
184+
requests_.clear();
185+
request_list_.clear();
186+
}
187+
188+
bool Loader::Callback::cancelled() const
189+
{
190+
return request_list_.empty();
191+
}
192+
193+
bool Loader::Callback::done() const
194+
{
195+
return requests_.empty();
196+
}
197+
198+
void Loader::Callback::wait() const
199+
{
200+
for (const auto& r : requests_)
201+
r->wait();
202+
}
203+
104204
// ************************************************************************************************
105205

106206
TypeHandle Loader::type_handle_;
107207

108208
Loader::Loader(ShowBase& base): impl_(std::make_unique<Impl>(base))
109209
{
210+
impl_->loader_ = ::Loader::get_global_ptr();
211+
212+
impl_->hook_ = "async_loader_" + std::to_string(Loader::Impl::loader_index_);
213+
Loader::Impl::loader_index_ += 1;
214+
accept(impl_->hook_, std::bind(&Impl::got_async_object, impl_.get(), std::placeholders::_1));
110215
}
111216

112217
#if !defined(_MSC_VER) || _MSC_VER >= 1900
113218
Loader::Loader(Loader&&) = default;
114219
#endif
115220

116-
Loader::~Loader() = default;
221+
Loader::~Loader()
222+
{
223+
ignore(impl_->hook_);
224+
impl_->loader_->stop_threads();
225+
}
117226

118227
#if !defined(_MSC_VER) || _MSC_VER >= 1900
119228
Loader& Loader::operator=(Loader&&) = default;
@@ -122,7 +231,8 @@ Loader& Loader::operator=(Loader&&) = default;
122231
NodePath Loader::load_model(const Filename& model_path, const LoaderOptions& loader_options,
123232
boost::optional<bool> no_cache, bool allow_instance, boost::optional<bool> ok_missing)
124233
{
125-
return load_model(std::vector<Filename>{model_path}, loader_options, no_cache, allow_instance, ok_missing).front();
234+
return load_model(std::vector<Filename>{model_path}, loader_options, no_cache,
235+
allow_instance, ok_missing).front();
126236
}
127237

128238
std::vector<NodePath> Loader::load_model(const std::vector<Filename>& model_list, const LoaderOptions& loader_options,
@@ -134,6 +244,7 @@ std::vector<NodePath> Loader::load_model(const std::vector<Filename>& model_list
134244
bool this_ok_missing;
135245
impl_->pre_load_model(this_options, this_ok_missing, no_cache, allow_instance, ok_missing);
136246

247+
// We got no callback, so it's a synchronous load.
137248
std::vector<NodePath> result;
138249
for (const auto& model_path: model_list)
139250
{
@@ -151,6 +262,50 @@ std::vector<NodePath> Loader::load_model(const std::vector<Filename>& model_list
151262
return result;
152263
}
153264

265+
std::shared_ptr<Loader::Callback> Loader::load_model_async(const Filename& model_path, const LoaderOptions& loader_options,
266+
boost::optional<bool> no_cache, bool allow_instance, boost::optional<bool> ok_missing,
267+
const std::function<void(const std::vector<NodePath>&)>& callback,
268+
boost::optional<int> priority)
269+
{
270+
return load_model_async(std::vector<Filename>{model_path}, loader_options, no_cache,
271+
allow_instance, ok_missing);
272+
}
273+
274+
std::shared_ptr<Loader::Callback> Loader::load_model_async(const std::vector<Filename>& model_list, const LoaderOptions& loader_options,
275+
boost::optional<bool> no_cache, bool allow_instance, boost::optional<bool> ok_missing,
276+
const std::function<void(const std::vector<NodePath>&)>& callback,
277+
boost::optional<int> priority)
278+
{
279+
rppanda_showbase_cat.debug() << "Loading model: " << join_to_string(model_list) << std::endl;
280+
281+
LoaderOptions this_options(loader_options);
282+
bool this_ok_missing;
283+
impl_->pre_load_model(this_options, this_ok_missing, no_cache, allow_instance, ok_missing);
284+
285+
// We got a callback, so we want an asynchronous(threaded)
286+
// load.We'll return immediately, but when all of the
287+
// requested models have been loaded, we'll invoke the
288+
// callback(passing it the models on the parameter list).
289+
290+
auto cb = std::make_shared<Callback>(this, model_list.size(), callback);
291+
292+
size_t i = 0;
293+
for (const auto& model_path: model_list)
294+
{
295+
PT(AsyncTask) request = impl_->loader_->make_async_request(model_path, this_options);
296+
if (priority)
297+
request->set_priority(priority.value());
298+
request->set_done_event(impl_->hook_);
299+
impl_->loader_->load_async(request);
300+
cb->requests_.insert(request);
301+
cb->request_list_.push_back(request);
302+
impl_->requests_.insert({request.p(), {cb, i}});
303+
i += 1;
304+
}
305+
306+
return cb;
307+
}
308+
154309
PT(TextFont) Loader::load_font(const std::string& model_path,
155310
boost::optional<float> space_advance, boost::optional<float> line_height,
156311
boost::optional<float> point_size,

0 commit comments

Comments
 (0)