Commit a6b57269 authored by Stephen McGruer's avatar Stephen McGruer Committed by Commit Bot

[Code Health] Clarify PaintWorkletPaintDispatcher behaviour

This CL adds some documentation for PaintWorkletPaintDispatcher, and
also changes up the |painter_map_| to allow direct lookup using the
worklet id.

Bug: None
Change-Id: I4f60ddce561b9fbc415f12f7f0bfcdd7a2c173d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1625462Reviewed-by: default avatarXida Chen <xidachen@chromium.org>
Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Commit-Queue: Stephen McGruer <smcgruer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#662634}
parent 78112dae
...@@ -22,23 +22,28 @@ namespace blink { ...@@ -22,23 +22,28 @@ namespace blink {
const char PaintWorkletProxyClient::kSupplementName[] = const char PaintWorkletProxyClient::kSupplementName[] =
"PaintWorkletProxyClient"; "PaintWorkletProxyClient";
// static
PaintWorkletProxyClient* PaintWorkletProxyClient::From(WorkerClients* clients) {
return Supplement<WorkerClients>::From<PaintWorkletProxyClient>(clients);
}
// static // static
PaintWorkletProxyClient* PaintWorkletProxyClient::Create(Document* document, PaintWorkletProxyClient* PaintWorkletProxyClient::Create(Document* document,
int worklet_id) { int worklet_id) {
WebLocalFrameImpl* local_frame = WebLocalFrameImpl* local_frame =
WebLocalFrameImpl::FromFrame(document->GetFrame()); WebLocalFrameImpl::FromFrame(document->GetFrame());
PaintWorklet* paint_worklet = PaintWorklet::From(*document->domWindow()); PaintWorklet* paint_worklet = PaintWorklet::From(*document->domWindow());
scoped_refptr<PaintWorkletPaintDispatcher> compositor_painter_dispatcher = scoped_refptr<PaintWorkletPaintDispatcher> compositor_paint_dispatcher =
local_frame->LocalRootFrameWidget()->EnsureCompositorPaintDispatcher(); local_frame->LocalRootFrameWidget()->EnsureCompositorPaintDispatcher();
return MakeGarbageCollected<PaintWorkletProxyClient>( return MakeGarbageCollected<PaintWorkletProxyClient>(
worklet_id, paint_worklet, std::move(compositor_painter_dispatcher)); worklet_id, paint_worklet, std::move(compositor_paint_dispatcher));
} }
PaintWorkletProxyClient::PaintWorkletProxyClient( PaintWorkletProxyClient::PaintWorkletProxyClient(
int worklet_id, int worklet_id,
PaintWorklet* paint_worklet, PaintWorklet* paint_worklet,
scoped_refptr<PaintWorkletPaintDispatcher> compositor_paintee) scoped_refptr<PaintWorkletPaintDispatcher> paint_dispatcher)
: compositor_paintee_(std::move(compositor_paintee)), : paint_dispatcher_(std::move(paint_dispatcher)),
worklet_id_(worklet_id), worklet_id_(worklet_id),
state_(RunState::kUninitialized), state_(RunState::kUninitialized),
main_thread_runner_(Thread::MainThread()->GetTaskRunner()), main_thread_runner_(Thread::MainThread()->GetTaskRunner()),
...@@ -46,12 +51,6 @@ PaintWorkletProxyClient::PaintWorkletProxyClient( ...@@ -46,12 +51,6 @@ PaintWorkletProxyClient::PaintWorkletProxyClient(
DCHECK(IsMainThread()); DCHECK(IsMainThread());
} }
void PaintWorkletProxyClient::Trace(blink::Visitor* visitor) {
visitor->Trace(document_definition_map_);
Supplement<WorkerClients>::Trace(visitor);
PaintWorkletPainter::Trace(visitor);
}
void PaintWorkletProxyClient::AddGlobalScope(WorkletGlobalScope* global_scope) { void PaintWorkletProxyClient::AddGlobalScope(WorkletGlobalScope* global_scope) {
DCHECK(global_scope); DCHECK(global_scope);
DCHECK(global_scope->IsContextThread()); DCHECK(global_scope->IsContextThread());
...@@ -66,14 +65,14 @@ void PaintWorkletProxyClient::AddGlobalScope(WorkletGlobalScope* global_scope) { ...@@ -66,14 +65,14 @@ void PaintWorkletProxyClient::AddGlobalScope(WorkletGlobalScope* global_scope) {
return; return;
} }
// All the global scopes that share a single PaintWorkletProxyClient are // All the global scopes that share a single PaintWorkletProxyClient run on
// running on the same thread, and so we can just grab the task runner from // the same thread with the same scheduler. As such we can just grab a task
// the last one to call this function and use that. // runner from the last one to register.
scoped_refptr<base::SingleThreadTaskRunner> global_scope_runner = scoped_refptr<base::SingleThreadTaskRunner> global_scope_runner =
global_scope->GetThread()->GetTaskRunner(TaskType::kMiscPlatformAPI); global_scope->GetThread()->GetTaskRunner(TaskType::kMiscPlatformAPI);
state_ = RunState::kWorking; state_ = RunState::kWorking;
compositor_paintee_->RegisterPaintWorkletPainter(this, global_scope_runner); paint_dispatcher_->RegisterPaintWorkletPainter(this, global_scope_runner);
} }
void PaintWorkletProxyClient::RegisterCSSPaintDefinition( void PaintWorkletProxyClient::RegisterCSSPaintDefinition(
...@@ -122,9 +121,9 @@ void PaintWorkletProxyClient::RegisterCSSPaintDefinition( ...@@ -122,9 +121,9 @@ void PaintWorkletProxyClient::RegisterCSSPaintDefinition(
void PaintWorkletProxyClient::Dispose() { void PaintWorkletProxyClient::Dispose() {
if (state_ == RunState::kWorking) { if (state_ == RunState::kWorking) {
compositor_paintee_->UnregisterPaintWorkletPainter(this); paint_dispatcher_->UnregisterPaintWorkletPainter(worklet_id_);
} }
compositor_paintee_ = nullptr; paint_dispatcher_ = nullptr;
state_ = RunState::kDisposed; state_ = RunState::kDisposed;
...@@ -133,6 +132,12 @@ void PaintWorkletProxyClient::Dispose() { ...@@ -133,6 +132,12 @@ void PaintWorkletProxyClient::Dispose() {
global_scopes_.clear(); global_scopes_.clear();
} }
void PaintWorkletProxyClient::Trace(blink::Visitor* visitor) {
visitor->Trace(document_definition_map_);
Supplement<WorkerClients>::Trace(visitor);
PaintWorkletPainter::Trace(visitor);
}
sk_sp<PaintRecord> PaintWorkletProxyClient::Paint( sk_sp<PaintRecord> PaintWorkletProxyClient::Paint(
CompositorPaintWorkletInput* compositor_input) { CompositorPaintWorkletInput* compositor_input) {
// TODO: Can this happen? We don't register till all are here. // TODO: Can this happen? We don't register till all are here.
...@@ -162,11 +167,6 @@ sk_sp<PaintRecord> PaintWorkletProxyClient::Paint( ...@@ -162,11 +167,6 @@ sk_sp<PaintRecord> PaintWorkletProxyClient::Paint(
style_map, nullptr); style_map, nullptr);
} }
// static
PaintWorkletProxyClient* PaintWorkletProxyClient::From(WorkerClients* clients) {
return Supplement<WorkerClients>::From<PaintWorkletProxyClient>(clients);
}
void ProvidePaintWorkletProxyClientTo(WorkerClients* clients, void ProvidePaintWorkletProxyClientTo(WorkerClients* clients,
PaintWorkletProxyClient* client) { PaintWorkletProxyClient* client) {
clients->ProvideSupplement(client); clients->ProvideSupplement(client);
......
...@@ -18,12 +18,14 @@ namespace blink { ...@@ -18,12 +18,14 @@ namespace blink {
class WorkletGlobalScope; class WorkletGlobalScope;
// Mediates between a worklet-thread bound PaintWorkletGlobalScope and its // Mediates between the (multiple) PaintWorkletGlobalScopes on the worklet
// associated dispatchers. A PaintWorkletProxyClient is associated with a single // thread and the (single) PaintWorkletPaintDispatcher on the non-worklet
// global scope and one dispatcher to the compositor thread. // threads. PaintWorkletProxyClient is responsible both for informing the
// dispatcher about its existence once all global scopes are registered, as well
// as choosing the global scope to use for any given paint request.
// //
// This is constructed on the main thread but it is used in the worklet backing // This class is constructed on the main thread but it is used in the worklet
// thread. // backing thread.
class MODULES_EXPORT PaintWorkletProxyClient class MODULES_EXPORT PaintWorkletProxyClient
: public GarbageCollectedFinalized<PaintWorkletProxyClient>, : public GarbageCollectedFinalized<PaintWorkletProxyClient>,
public Supplement<WorkerClients>, public Supplement<WorkerClients>,
...@@ -32,8 +34,13 @@ class MODULES_EXPORT PaintWorkletProxyClient ...@@ -32,8 +34,13 @@ class MODULES_EXPORT PaintWorkletProxyClient
DISALLOW_COPY_AND_ASSIGN(PaintWorkletProxyClient); DISALLOW_COPY_AND_ASSIGN(PaintWorkletProxyClient);
public: public:
// blink::Supplement hook to retrieve the PaintWorkletProxyClient for a given
// WorkerClients.
static const char kSupplementName[]; static const char kSupplementName[];
static PaintWorkletProxyClient* From(WorkerClients*);
// Create the PaintWorkletProxyClient for a given PaintWorklet, represented by
// its unique |worklet_id|.
static PaintWorkletProxyClient* Create(Document*, int worklet_id); static PaintWorkletProxyClient* Create(Document*, int worklet_id);
PaintWorkletProxyClient( PaintWorkletProxyClient(
...@@ -42,26 +49,31 @@ class MODULES_EXPORT PaintWorkletProxyClient ...@@ -42,26 +49,31 @@ class MODULES_EXPORT PaintWorkletProxyClient
scoped_refptr<PaintWorkletPaintDispatcher> compositor_paintee); scoped_refptr<PaintWorkletPaintDispatcher> compositor_paintee);
~PaintWorkletProxyClient() override = default; ~PaintWorkletProxyClient() override = default;
void Trace(blink::Visitor*) override; // PaintWorkletPainter implementation.
// PaintWorkletPainter implementation
int GetWorkletId() const override { return worklet_id_; } int GetWorkletId() const override { return worklet_id_; }
sk_sp<PaintRecord> Paint(CompositorPaintWorkletInput*) override; sk_sp<PaintRecord> Paint(CompositorPaintWorkletInput*) override;
// Add a global scope to the PaintWorkletProxyClient.
virtual void AddGlobalScope(WorkletGlobalScope*); virtual void AddGlobalScope(WorkletGlobalScope*);
const Vector<CrossThreadPersistent<PaintWorkletGlobalScope>>&
GetGlobalScopesForTesting() const {
return global_scopes_;
}
// Register a paint definition for this PaintWorklet.
// See https://drafts.css-houdini.org/css-paint-api-1/#paint-definition
void RegisterCSSPaintDefinition(const String& name, void RegisterCSSPaintDefinition(const String& name,
CSSPaintDefinition*, CSSPaintDefinition*,
ExceptionState&); ExceptionState&);
// Dispose of the PaintWorkletProxyClient. Called when the worklet global
// scopes are being torn down. May be called once per global scope - calls
// after the first have no effect.
void Dispose(); void Dispose();
static PaintWorkletProxyClient* From(WorkerClients*); void Trace(blink::Visitor*) override;
// Hooks for testing.
const Vector<CrossThreadPersistent<PaintWorkletGlobalScope>>&
GetGlobalScopesForTesting() const {
return global_scopes_;
}
const HeapHashMap<String, Member<DocumentPaintDefinition>>& const HeapHashMap<String, Member<DocumentPaintDefinition>>&
DocumentDefinitionMapForTesting() const { DocumentDefinitionMapForTesting() const {
return document_definition_map_; return document_definition_map_;
...@@ -81,16 +93,38 @@ class MODULES_EXPORT PaintWorkletProxyClient ...@@ -81,16 +93,38 @@ class MODULES_EXPORT PaintWorkletProxyClient
FRIEND_TEST_ALL_PREFIXES(PaintWorkletProxyClientTest, FRIEND_TEST_ALL_PREFIXES(PaintWorkletProxyClientTest,
PaintWorkletProxyClientConstruction); PaintWorkletProxyClientConstruction);
scoped_refptr<PaintWorkletPaintDispatcher> compositor_paintee_; // The |paint_dispatcher_| is shared between all PaintWorklets on the same
// Renderer process, and is responsible for dispatching paint calls from the
// non-worklet threads to the correct PaintWorkletProxyClient on its worklet
// thread. PaintWorkletProxyClient requires a reference to the dispatcher in
// order to register and unregister itself.
scoped_refptr<PaintWorkletPaintDispatcher> paint_dispatcher_;
// The unique id for the PaintWorklet that this class is a proxy client for.
const int worklet_id_; const int worklet_id_;
// The set of global scopes registered for this PaintWorklet. Multiple global
// scopes are used to enforce statelessness - paint instances may have their
// global scope changed at random which means they cannot easily store state.
Vector<CrossThreadPersistent<PaintWorkletGlobalScope>> global_scopes_; Vector<CrossThreadPersistent<PaintWorkletGlobalScope>> global_scopes_;
// The current state of the proxy client. PaintWorkletProxyClient is initially
// uninitialized. Once all global scopes are registered, it is considered
// working - unless it is disposed of before this happens in which case it
// stays in the disposed state.
enum RunState { kUninitialized, kWorking, kDisposed } state_; enum RunState { kUninitialized, kWorking, kDisposed } state_;
// Stores the definitions for each paint that is registered. // Stores the paint definitions as they are registered from the global scopes.
// For a given named paint definition, all global scopes must report the same
// DocumentPaintDefinition or the definition is invalid. Additionally we
// cannot tell the main thread about a paint definition until all global
// scopes have registered it.
HeapHashMap<String, Member<DocumentPaintDefinition>> document_definition_map_; HeapHashMap<String, Member<DocumentPaintDefinition>> document_definition_map_;
// Used for OffMainThreadPaintWorklet; we post to the main-thread PaintWorklet // The main thread needs to know about registered paint definitions so that it
// instance to store the definitions for each paint that is registered. // can invalidate any associated paint objects and correctly create the paint
// instance input state for the object, etc. We communicate with it via a
// handle to the PaintWorklet called via a stored task runner.
scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner_; scoped_refptr<base::SingleThreadTaskRunner> main_thread_runner_;
CrossThreadWeakPersistent<PaintWorklet> paint_worklet_; CrossThreadWeakPersistent<PaintWorklet> paint_worklet_;
}; };
......
...@@ -108,7 +108,7 @@ TEST_F(PaintWorkletProxyClientTest, PaintWorkletProxyClientConstruction) { ...@@ -108,7 +108,7 @@ TEST_F(PaintWorkletProxyClientTest, PaintWorkletProxyClientConstruction) {
PaintWorkletProxyClient* proxy_client = PaintWorkletProxyClient* proxy_client =
MakeGarbageCollected<PaintWorkletProxyClient>(1, nullptr, nullptr); MakeGarbageCollected<PaintWorkletProxyClient>(1, nullptr, nullptr);
EXPECT_EQ(proxy_client->worklet_id_, 1); EXPECT_EQ(proxy_client->worklet_id_, 1);
EXPECT_EQ(proxy_client->compositor_paintee_, nullptr); EXPECT_EQ(proxy_client->paint_dispatcher_, nullptr);
scoped_refptr<PaintWorkletPaintDispatcher> dispatcher = scoped_refptr<PaintWorkletPaintDispatcher> dispatcher =
base::MakeRefCounted<PaintWorkletPaintDispatcher>(); base::MakeRefCounted<PaintWorkletPaintDispatcher>();
...@@ -116,7 +116,7 @@ TEST_F(PaintWorkletProxyClientTest, PaintWorkletProxyClientConstruction) { ...@@ -116,7 +116,7 @@ TEST_F(PaintWorkletProxyClientTest, PaintWorkletProxyClientConstruction) {
proxy_client = MakeGarbageCollected<PaintWorkletProxyClient>( proxy_client = MakeGarbageCollected<PaintWorkletProxyClient>(
1, nullptr, std::move(dispatcher)); 1, nullptr, std::move(dispatcher));
EXPECT_EQ(proxy_client->worklet_id_, 1); EXPECT_EQ(proxy_client->worklet_id_, 1);
EXPECT_NE(proxy_client->compositor_paintee_, nullptr); EXPECT_NE(proxy_client->paint_dispatcher_, nullptr);
} }
void RunAddGlobalScopesTestOnWorklet( void RunAddGlobalScopesTestOnWorklet(
......
...@@ -49,21 +49,20 @@ void PaintWorkletPaintDispatcher::RegisterPaintWorkletPainter( ...@@ -49,21 +49,20 @@ void PaintWorkletPaintDispatcher::RegisterPaintWorkletPainter(
TRACE_EVENT0("cc", TRACE_EVENT0("cc",
"PaintWorkletPaintDispatcher::RegisterPaintWorkletPainter"); "PaintWorkletPaintDispatcher::RegisterPaintWorkletPainter");
DCHECK(painter); int worklet_id = painter->GetWorkletId();
MutexLocker lock(painter_map_mutex_); MutexLocker lock(painter_map_mutex_);
DCHECK(painter_map_.find(painter) == painter_map_.end()); DCHECK(painter_map_.find(worklet_id) == painter_map_.end());
painter_map_.insert(painter, painter_runner); painter_map_.insert(worklet_id, std::make_pair(painter, painter_runner));
} }
void PaintWorkletPaintDispatcher::UnregisterPaintWorkletPainter( void PaintWorkletPaintDispatcher::UnregisterPaintWorkletPainter(
PaintWorkletPainter* painter) { int worklet_id) {
TRACE_EVENT0("cc", TRACE_EVENT0("cc",
"PaintWorkletPaintDispatcher::" "PaintWorkletPaintDispatcher::"
"UnregisterPaintWorkletPainter"); "UnregisterPaintWorkletPainter");
DCHECK(painter);
MutexLocker lock(painter_map_mutex_); MutexLocker lock(painter_map_mutex_);
DCHECK(painter_map_.find(painter) != painter_map_.end()); DCHECK(painter_map_.find(worklet_id) != painter_map_.end());
painter_map_.erase(painter); painter_map_.erase(worklet_id);
} }
// TODO(xidachen): we should bundle all PaintWorkletInputs and send them to the // TODO(xidachen): we should bundle all PaintWorkletInputs and send them to the
...@@ -86,27 +85,25 @@ sk_sp<cc::PaintRecord> PaintWorkletPaintDispatcher::Paint( ...@@ -86,27 +85,25 @@ sk_sp<cc::PaintRecord> PaintWorkletPaintDispatcher::Paint(
base::WaitableEvent done_event; base::WaitableEvent done_event;
for (auto& pair : copied_painter_map) { auto it = copied_painter_map.find(input->WorkletId());
if (pair.key->GetWorkletId() != input->WorkletId()) if (it == copied_painter_map.end())
continue; return output;
PaintWorkletPainter* painter = pair.key;
scoped_refptr<base::SingleThreadTaskRunner> task_runner = pair.value; PaintWorkletPainter* painter = it->value.first;
scoped_refptr<base::SingleThreadTaskRunner> task_runner = it->value.second;
DCHECK(!task_runner->BelongsToCurrentThread()); DCHECK(!task_runner->BelongsToCurrentThread());
std::unique_ptr<AutoSignal> done = std::unique_ptr<AutoSignal> done = std::make_unique<AutoSignal>(&done_event);
std::make_unique<AutoSignal>(&done_event);
PostCrossThreadTask(
PostCrossThreadTask( *task_runner, FROM_HERE,
*task_runner, FROM_HERE, CrossThreadBindOnce(
CrossThreadBindOnce( [](PaintWorkletPainter* painter, cc::PaintWorkletInput* input,
[](PaintWorkletPainter* painter, cc::PaintWorkletInput* input, std::unique_ptr<AutoSignal> completion,
std::unique_ptr<AutoSignal> completion, sk_sp<cc::PaintRecord>* output) {
sk_sp<cc::PaintRecord>* output) { *output = painter->Paint(input);
*output = painter->Paint(input); },
}, WrapCrossThreadPersistent(painter), CrossThreadUnretained(input),
WrapCrossThreadPersistent(painter), CrossThreadUnretained(input), WTF::Passed(std::move(done)), CrossThreadUnretained(&output)));
WTF::Passed(std::move(done)), CrossThreadUnretained(&output)));
}
done_event.Wait(); done_event.Wait();
......
...@@ -20,9 +20,17 @@ ...@@ -20,9 +20,17 @@
namespace blink { namespace blink {
// The dispatcher receives JS paint callback from the compositor, and dispatch // PaintWorkletPaintDispatcher is responsible for mediating between the raster
// the callback to the painter (on the paint worklet thread) that is associated // threads and the PaintWorklet thread(s). It receives requests from raster
// with the given paint image. // threads to paint a paint class instance represented by a PaintWorkletInput,
// dispatches the input to the appropriate PaintWorklet, synchronously receives
// the result, and passes it back to the raster thread.
//
// Each PaintWorklet (there is one per frame, either same-origin or
// same-process-cross-origin) has a backing thread, which may be shared between
// worklets, and a scheduler, which is not shared. All PaintWorklets for a
// single renderer process share one PaintWorkletPaintDispatcher on the
// compositor side.
class PLATFORM_EXPORT PaintWorkletPaintDispatcher class PLATFORM_EXPORT PaintWorkletPaintDispatcher
: public ThreadSafeRefCounted<PaintWorkletPaintDispatcher> { : public ThreadSafeRefCounted<PaintWorkletPaintDispatcher> {
public: public:
...@@ -32,30 +40,39 @@ class PLATFORM_EXPORT PaintWorkletPaintDispatcher ...@@ -32,30 +40,39 @@ class PLATFORM_EXPORT PaintWorkletPaintDispatcher
PaintWorkletPaintDispatcher() = default; PaintWorkletPaintDispatcher() = default;
// Interface for use by the PaintWorklet thread(s) to request calls. // Dispatches a single paint class instance - represented by a
// (To the given Painter on the given TaskRunner.) // PaintWorkletInput - to the appropriate PaintWorklet thread, and blocks
void RegisterPaintWorkletPainter( // until it receives the result.
PaintWorkletPainter*,
scoped_refptr<base::SingleThreadTaskRunner> mutator_runner);
void UnregisterPaintWorkletPainter(PaintWorkletPainter*);
sk_sp<cc::PaintRecord> Paint(cc::PaintWorkletInput*); sk_sp<cc::PaintRecord> Paint(cc::PaintWorkletInput*);
// Register and unregister a PaintWorklet (represented in this context by a
// PaintWorkletPainter). A given PaintWorklet is registered once all its
// global scopes have been created, and is usually only unregistered when the
// associated PaintWorklet thread is being torn down.
//
// The passed in PaintWorkletPainter* should only be used on the given
// base::SingleThreadTaskRunner.
using PaintWorkletId = int;
void RegisterPaintWorkletPainter(PaintWorkletPainter*,
scoped_refptr<base::SingleThreadTaskRunner>);
void UnregisterPaintWorkletPainter(PaintWorkletId);
using PaintWorkletPainterToTaskRunnerMap = using PaintWorkletPainterToTaskRunnerMap =
HashMap<CrossThreadPersistent<PaintWorkletPainter>, HashMap<PaintWorkletId,
scoped_refptr<base::SingleThreadTaskRunner>>; std::pair<CrossThreadPersistent<PaintWorkletPainter>,
scoped_refptr<base::SingleThreadTaskRunner>>>;
const PaintWorkletPainterToTaskRunnerMap& PainterMapForTesting() const { const PaintWorkletPainterToTaskRunnerMap& PainterMapForTesting() const {
return painter_map_; return painter_map_;
} }
private: private:
// We can have more than one task-runner because using a worklet inside a // This class handles paint class instances for multiple PaintWorklets. These
// frame with a different origin causes a new global scope => new thread. // are disambiguated via the PaintWorklets unique id; this map exists to do
// that disambiguation.
PaintWorkletPainterToTaskRunnerMap painter_map_; PaintWorkletPainterToTaskRunnerMap painter_map_;
// The (Un)registerPaintWorkletPainter comes from the worklet thread, and the // The (Un)registerPaintWorkletPainter comes from the worklet thread, and the
// Paint call is initiated from the raster threads, this mutex ensures that // Paint call is initiated from the raster threads - this mutex ensures that
// accessing / updating the |painter_map_| is thread safe. // accessing / updating the |painter_map_| is thread safe.
Mutex painter_map_mutex_; Mutex painter_map_mutex_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment