Revert "SL-16220: Merge branch 'origin/DRTVWR-546' into glthread"

This reverts commit 5188a26a85, reversing
changes made to 819088563e.
master
Dave Houlton 2021-11-15 09:25:35 -07:00
parent aeed774ff9
commit 029b41c041
28 changed files with 770 additions and 849 deletions

View File

@ -119,8 +119,8 @@ set(llcommon_SOURCE_FILES
lluriparser.cpp
lluuid.cpp
llworkerthread.cpp
timing.cpp
u64.cpp
threadpool.cpp
workqueue.cpp
StackWalker.cpp
)
@ -256,7 +256,6 @@ set(llcommon_HEADER_FILES
lockstatic.h
stdtypes.h
stringize.h
threadpool.h
threadsafeschedule.h
timer.h
tuple.h

View File

@ -847,28 +847,22 @@ template<class T>
class LLSimpleton
{
public:
template <typename... ARGS>
static void createInstance(ARGS&&... args)
{
static T* sInstance;
static void createInstance()
{
llassert(sInstance == nullptr);
sInstance = new T(std::forward<ARGS>(args)...);
sInstance = new T();
}
static inline T* getInstance() { return sInstance; }
static inline T& instance() { return *getInstance(); }
static inline bool instanceExists() { return sInstance != nullptr; }
static void deleteSingleton()
{
delete sInstance;
sInstance = nullptr;
static void deleteSingleton() {
delete sInstance;
sInstance = nullptr;
}
private:
static T* sInstance;
};
template <class T>
T* LLSimpleton<T>::sInstance{ nullptr };
#endif

View File

@ -85,8 +85,8 @@ public:
LLThreadSafeQueue(U32 capacity = 1024);
virtual ~LLThreadSafeQueue() {}
// Add an element to the queue (will block if the queue has reached
// capacity).
// Add an element to the queue (will block if the queue has
// reached capacity).
//
// This call will raise an interrupt error if the queue is closed while
// the caller is blocked.
@ -95,11 +95,6 @@ public:
// legacy name
void pushFront(ElementT const & element) { return push(element); }
// Add an element to the queue (will block if the queue has reached
// capacity). Return false if the queue is closed before push is possible.
template <typename T>
bool pushIfOpen(T&& element);
// Try to add an element to the queue without blocking. Returns
// true only if the element was actually added.
template <typename T>
@ -316,8 +311,8 @@ bool LLThreadSafeQueue<ElementT, QueueT>::push_(lock_t& lock, T&& element)
template <typename ElementT, typename QueueT>
template <typename T>
bool LLThreadSafeQueue<ElementT, QueueT>::pushIfOpen(T&& element)
template<typename T>
void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element)
{
lock_t lock1(mLock);
while (true)
@ -326,10 +321,12 @@ bool LLThreadSafeQueue<ElementT, QueueT>::pushIfOpen(T&& element)
// drained or not: the moment either end calls close(), further push()
// operations will fail.
if (mClosed)
return false;
{
LLTHROW(LLThreadSafeQueueInterrupt());
}
if (push_(lock1, std::forward<T>(element)))
return true;
return;
// Storage Full. Wait for signal.
mCapacityCond.wait(lock1);
@ -337,17 +334,6 @@ bool LLThreadSafeQueue<ElementT, QueueT>::pushIfOpen(T&& element)
}
template <typename ElementT, typename QueueT>
template<typename T>
void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element)
{
if (! pushIfOpen(std::forward<T>(element)))
{
LLTHROW(LLThreadSafeQueueInterrupt());
}
}
template<typename ElementT, typename QueueT>
template<typename T>
bool LLThreadSafeQueue<ElementT, QueueT>::tryPush(T&& element)

View File

@ -46,11 +46,11 @@ namespace tut
// the real time required for each push() call. Explicitly increment
// the timestamp for each one -- but since we're passing explicit
// timestamps, make the queue reorder them.
queue.push(Queue::TimeTuple(Queue::Clock::now() + 200ms, "ghi"));
queue.push(Queue::TimeTuple(Queue::Clock::now() + 20ms, "ghi"));
// Given the various push() overloads, you have to match the type
// exactly: conversions are ambiguous.
queue.push("abc"s);
queue.push(Queue::Clock::now() + 100ms, "def");
queue.push(Queue::Clock::now() + 10ms, "def");
queue.close();
auto entry = queue.pop();
ensure_equals("failed to pop first", std::get<0>(entry), "abc"s);

View File

@ -20,10 +20,7 @@
// external library headers
// other Linden headers
#include "../test/lltut.h"
#include "../test/catch_and_store_what_in.h"
#include "llcond.h"
#include "llcoros.h"
#include "lleventcoro.h"
#include "llstring.h"
#include "stringize.h"
@ -141,8 +138,7 @@ namespace tut
[](){ return 17; },
// Note that a postTo() *callback* can safely bind a reference to
// a variable on the invoking thread, because the callback is run
// on the invoking thread. (Of course the bound variable must
// survive until the callback is called.)
// on the invoking thread.
[&result](int i){ result = i; });
// this should post the callback to main
qptr->runOne();
@ -160,70 +156,4 @@ namespace tut
main.runPending();
ensure_equals("failed to run string callback", alpha, "abc");
}
template<> template<>
void object::test<5>()
{
set_test_name("postTo with void return");
WorkQueue main("main");
auto qptr = WorkQueue::getInstance("queue");
std::string observe;
main.postTo(
qptr,
// The ONLY reason we can get away with binding a reference to
// 'observe' in our work callable is because we're directly
// calling qptr->runOne() on this same thread. It would be a
// mistake to do that if some other thread were servicing 'queue'.
[&observe](){ observe = "queue"; },
[&observe](){ observe.append(";main"); });
qptr->runOne();
main.runOne();
ensure_equals("failed to run both lambdas", observe, "queue;main");
}
template<> template<>
void object::test<6>()
{
set_test_name("waitForResult");
std::string stored;
// Try to call waitForResult() on this thread's main coroutine. It
// should throw because the main coroutine must service the queue.
auto what{ catch_what<WorkQueue::Error>(
[this, &stored](){ stored = queue.waitForResult(
[](){ return "should throw"; }); }) };
ensure("lambda should not have run", stored.empty());
ensure_not("waitForResult() should have thrown", what.empty());
ensure(STRINGIZE("should mention waitForResult: " << what),
what.find("waitForResult") != std::string::npos);
// Call waitForResult() on a coroutine, with a string result.
LLCoros::instance().launch(
"waitForResult string",
[this, &stored]()
{ stored = queue.waitForResult(
[](){ return "string result"; }); });
llcoro::suspend();
// Nothing will have happened yet because, even if the coroutine did
// run immediately, all it did was to queue the inner lambda on
// 'queue'. Service it.
queue.runOne();
llcoro::suspend();
ensure_equals("bad waitForResult return", stored, "string result");
// Call waitForResult() on a coroutine, with a void callable.
stored.clear();
bool done = false;
LLCoros::instance().launch(
"waitForResult void",
[this, &stored, &done]()
{
queue.waitForResult([&stored](){ stored = "ran"; });
done = true;
});
llcoro::suspend();
queue.runOne();
llcoro::suspend();
ensure_equals("didn't run coroutine", stored, "ran");
ensure("void waitForResult() didn't return", done);
}
} // namespace tut

View File

@ -1,80 +0,0 @@
/**
* @file threadpool.cpp
* @author Nat Goodspeed
* @date 2021-10-21
* @brief Implementation for threadpool.
*
* $LicenseInfo:firstyear=2021&license=viewerlgpl$
* Copyright (c) 2021, Linden Research, Inc.
* $/LicenseInfo$
*/
// Precompiled header
#include "linden_common.h"
// associated header
#include "threadpool.h"
// STL headers
// std headers
// external library headers
// other Linden headers
#include "llerror.h"
#include "llevents.h"
#include "stringize.h"
LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity):
mQueue(name, capacity),
mName("ThreadPool:" + name)
{
for (size_t i = 0; i < threads; ++i)
{
std::string tname{ STRINGIZE(mName << ':' << (i+1) << '/' << threads) };
mThreads.emplace_back(tname, [this, tname](){ run(tname); });
}
// Listen on "LLApp", and when the app is shutting down, close the queue
// and join the workers.
LLEventPumps::instance().obtain("LLApp").listen(
mName,
[this](const LLSD& stat)
{
std::string status(stat["status"]);
if (status != "running")
{
// viewer is starting shutdown -- proclaim the end is nigh!
LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL;
close();
}
return false;
});
}
LL::ThreadPool::~ThreadPool()
{
close();
}
void LL::ThreadPool::close()
{
if (! mQueue.isClosed())
{
LL_DEBUGS("ThreadPool") << mName << " closing queue and joining threads" << LL_ENDL;
mQueue.close();
for (auto& pair: mThreads)
{
LL_DEBUGS("ThreadPool") << mName << " waiting on thread " << pair.first << LL_ENDL;
pair.second.join();
}
LL_DEBUGS("ThreadPool") << mName << " shutdown complete" << LL_ENDL;
}
}
void LL::ThreadPool::run(const std::string& name)
{
LL_DEBUGS("ThreadPool") << name << " starting" << LL_ENDL;
run();
LL_DEBUGS("ThreadPool") << name << " stopping" << LL_ENDL;
}
void LL::ThreadPool::run()
{
mQueue.runUntilClose();
}

View File

@ -1,62 +0,0 @@
/**
* @file threadpool.h
* @author Nat Goodspeed
* @date 2021-10-21
* @brief ThreadPool configures a WorkQueue along with a pool of threads to
* service it.
*
* $LicenseInfo:firstyear=2021&license=viewerlgpl$
* Copyright (c) 2021, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_THREADPOOL_H)
#define LL_THREADPOOL_H
#include "workqueue.h"
#include <string>
#include <thread>
#include <utility> // std::pair
#include <vector>
namespace LL
{
class ThreadPool
{
public:
/**
* Pass ThreadPool a string name. This can be used to look up the
* relevant WorkQueue.
*/
ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024);
virtual ~ThreadPool();
/**
* ThreadPool listens for application shutdown messages on the "LLApp"
* LLEventPump. Call close() to shut down this ThreadPool early.
*/
void close();
std::string getName() const { return mName; }
size_t getWidth() const { return mThreads.size(); }
/// obtain a non-const reference to the WorkQueue to post work to it
WorkQueue& getQueue() { return mQueue; }
/**
* Override run() if you need special processing. The default run()
* implementation simply calls WorkQueue::runUntilClose().
*/
virtual void run();
private:
void run(const std::string& name);
WorkQueue mQueue;
std::string mName;
std::vector<std::pair<std::string, std::thread>> mThreads;
};
} // namespace LL
#endif /* ! defined(LL_THREADPOOL_H) */

25
indra/llcommon/timing.cpp Normal file
View File

@ -0,0 +1,25 @@
/**
* @file timing.cpp
* @brief This file will be deprecated in the future.
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/

View File

@ -26,9 +26,8 @@
using Mutex = LLCoros::Mutex;
using Lock = LLCoros::LockType;
LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity):
super(makeName(name)),
mQueue(capacity)
LL::WorkQueue::WorkQueue(const std::string& name):
super(makeName(name))
{
// TODO: register for "LLApp" events so we can implicitly close() on
// viewer shutdown.
@ -39,21 +38,6 @@ void LL::WorkQueue::close()
mQueue.close();
}
size_t LL::WorkQueue::size()
{
return mQueue.size();
}
bool LL::WorkQueue::isClosed()
{
return mQueue.isClosed();
}
bool LL::WorkQueue::done()
{
return mQueue.done();
}
void LL::WorkQueue::runUntilClose()
{
try
@ -144,13 +128,3 @@ void LL::WorkQueue::error(const std::string& msg)
{
LL_ERRS("WorkQueue") << msg << LL_ENDL;
}
void LL::WorkQueue::checkCoroutine(const std::string& method)
{
// By convention, the default coroutine on each thread has an empty name
// string. See also LLCoros::logname().
if (LLCoros::getName().empty())
{
LLTHROW(Error("Do not call " + method + " from a thread's default coroutine"));
}
}

View File

@ -12,14 +12,14 @@
#if ! defined(LL_WORKQUEUE_H)
#define LL_WORKQUEUE_H
#include "llcoros.h"
#include "llexception.h"
#include "llinstancetracker.h"
#include "threadsafeschedule.h"
#include <chrono>
#include <exception> // std::current_exception
#include <functional> // std::function
#include <queue>
#include <string>
#include <utility> // std::pair
#include <vector>
namespace LL
{
@ -45,16 +45,11 @@ namespace LL
using TimedWork = Queue::TimeTuple;
using Closed = Queue::Closed;
struct Error: public LLException
{
Error(const std::string& what): LLException(what) {}
};
/**
* You may omit the WorkQueue name, in which case a unique name is
* synthesized; for practical purposes that makes it anonymous.
*/
WorkQueue(const std::string& name = std::string(), size_t capacity=1024);
WorkQueue(const std::string& name = std::string());
/**
* Since the point of WorkQueue is to pass work to some other worker
@ -64,36 +59,15 @@ namespace LL
*/
void close();
/**
* WorkQueue supports multiple producers and multiple consumers. In
* the general case it's misleading to test size(), since any other
* thread might change it the nanosecond the lock is released. On that
* basis, some might argue against publishing a size() method at all.
*
* But there are two specific cases in which a test based on size()
* might be reasonable:
*
* * If you're the only producer, noticing that size() == 0 is
* meaningful.
* * If you're the only consumer, noticing that size() > 0 is
* meaningful.
*/
size_t size();
/// producer end: are we prevented from pushing any additional items?
bool isClosed();
/// consumer end: are we done, is the queue entirely drained?
bool done();
/*---------------------- fire and forget API -----------------------*/
/// fire-and-forget, but at a particular (future?) time
template <typename CALLABLE>
void post(const TimePoint& time, CALLABLE&& callable)
{
// Defer reifying an arbitrary CALLABLE until we hit this or
// postIfOpen(). All other methods should accept CALLABLEs of
// arbitrary type to avoid multiple levels of std::function
// indirection.
// Defer reifying an arbitrary CALLABLE until we hit this method.
// All other methods should accept CALLABLEs of arbitrary type to
// avoid multiple levels of std::function indirection.
mQueue.push(TimedWork(time, std::move(callable)));
}
@ -108,47 +82,6 @@ namespace LL
post(TimePoint::clock::now(), std::move(callable));
}
/**
* post work for a particular time, unless the queue is closed before
* we can post
*/
template <typename CALLABLE>
bool postIfOpen(const TimePoint& time, CALLABLE&& callable)
{
// Defer reifying an arbitrary CALLABLE until we hit this or
// post(). All other methods should accept CALLABLEs of arbitrary
// type to avoid multiple levels of std::function indirection.
return mQueue.pushIfOpen(TimedWork(time, std::move(callable)));
}
/**
* post work, unless the queue is closed before we can post
*/
template <typename CALLABLE>
bool postIfOpen(CALLABLE&& callable)
{
return postIfOpen(TimePoint::clock::now(), std::move(callable));
}
/**
* Post work to be run at a specified time to another WorkQueue, which
* may or may not still exist and be open. Return true if we were able
* to post.
*/
template <typename CALLABLE>
static bool postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable);
/**
* Post work to another WorkQueue, which may or may not still exist
* and be open. Return true if we were able to post.
*/
template <typename CALLABLE>
static bool postMaybe(weak_t target, CALLABLE&& callable)
{
return postMaybe(target, TimePoint::clock::now(),
std::forward<CALLABLE>(callable));
}
/**
* Launch a callable returning bool that will trigger repeatedly at
* specified interval, until the callable returns false.
@ -182,8 +115,63 @@ namespace LL
// Studio compile errors that seem utterly unrelated to this source
// code.
template <typename CALLABLE, typename FOLLOWUP>
bool postTo(weak_t target,
const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback);
bool postTo(WorkQueue::weak_t target,
const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback)
{
// We're being asked to post to the WorkQueue at target.
// target is a weak_ptr: have to lock it to check it.
auto tptr = target.lock();
if (! tptr)
// can't post() if the target WorkQueue has been destroyed
return false;
// Here we believe target WorkQueue still exists. Post to it a
// lambda that packages our callable, our callback and a weak_ptr
// to this originating WorkQueue.
tptr->post(
time,
[reply = super::getWeak(),
callable = std::move(callable),
callback = std::move(callback)]
()
{
// Call the callable in any case -- but to minimize
// copying the result, immediately bind it into a reply
// lambda. The reply lambda also binds the original
// callback, so that when we, the originating WorkQueue,
// finally receive and process the reply lambda, we'll
// call the bound callback with the bound result -- on the
// same thread that originally called postTo().
auto rlambda =
[result = callable(),
callback = std::move(callback)]
()
{ callback(std::move(result)); };
// Check if this originating WorkQueue still exists.
// Remember, the outer lambda is now running on a thread
// servicing the target WorkQueue, and real time has
// elapsed since postTo()'s tptr->post() call.
// reply is a weak_ptr: have to lock it to check it.
auto rptr = reply.lock();
if (rptr)
{
// Only post reply lambda if the originating WorkQueue
// still exists. If not -- who would we tell? Log it?
try
{
rptr->post(std::move(rlambda));
}
catch (const Closed&)
{
// Originating WorkQueue might still exist, but
// might be Closed. Same thing: just discard the
// callback.
}
}
});
// looks like we were able to post()
return true;
}
/**
* Post work to another WorkQueue, requesting a specific callback to
@ -193,36 +181,10 @@ namespace LL
* inaccessible.
*/
template <typename CALLABLE, typename FOLLOWUP>
bool postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback)
bool postTo(WorkQueue::weak_t target,
CALLABLE&& callable, FOLLOWUP&& callback)
{
return postTo(target, TimePoint::clock::now(),
std::move(callable), std::move(callback));
}
/**
* Post work to another WorkQueue to be run at a specified time,
* blocking the calling coroutine until then, returning the result to
* caller on completion.
*
* In general, we assume that each thread's default coroutine is busy
* servicing its WorkQueue or whatever. To try to prevent mistakes, we
* forbid calling waitForResult() from a thread's default coroutine.
*/
template <typename CALLABLE>
auto waitForResult(const TimePoint& time, CALLABLE&& callable);
/**
* Post work to another WorkQueue, blocking the calling coroutine
* until then, returning the result to caller on completion.
*
* In general, we assume that each thread's default coroutine is busy
* servicing its WorkQueue or whatever. To try to prevent mistakes, we
* forbid calling waitForResult() from a thread's default coroutine.
*/
template <typename CALLABLE>
auto waitForResult(CALLABLE&& callable)
{
return waitForResult(TimePoint::clock::now(), std::move(callable));
return postTo(target, TimePoint::clock::now(), std::move(callable), std::move(callback));
}
/*--------------------------- worker API ---------------------------*/
@ -270,23 +232,6 @@ namespace LL
bool runUntil(const TimePoint& until);
private:
template <typename CALLABLE, typename FOLLOWUP>
static auto makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback);
/// general case: arbitrary C++ return type
template <typename CALLABLE, typename FOLLOWUP, typename RETURNTYPE>
struct MakeReplyLambda;
/// specialize for CALLABLE returning void
template <typename CALLABLE, typename FOLLOWUP>
struct MakeReplyLambda<CALLABLE, FOLLOWUP, void>;
/// general case: arbitrary C++ return type
template <typename CALLABLE, typename RETURNTYPE>
struct WaitForResult;
/// specialize for CALLABLE returning void
template <typename CALLABLE>
struct WaitForResult<CALLABLE, void>;
static void checkCoroutine(const std::string& method);
static void error(const std::string& msg);
static std::string makeName(const std::string& name);
void callWork(const Queue::DataTuple& work);
@ -308,8 +253,8 @@ namespace LL
{
public:
// bind the desired data
BackJack(weak_t target,
const TimePoint& start,
BackJack(WorkQueue::weak_t target,
const WorkQueue::TimePoint& start,
const std::chrono::duration<Rep, Period>& interval,
CALLABLE&& callable):
mTarget(target),
@ -356,8 +301,8 @@ namespace LL
}
private:
weak_t mTarget;
TimePoint mStart;
WorkQueue::weak_t mTarget;
WorkQueue::TimePoint mStart;
std::chrono::duration<Rep, Period> mInterval;
CALLABLE mCallable;
};
@ -385,187 +330,6 @@ namespace LL
getWeak(), TimePoint::clock::now(), interval, std::move(callable)));
}
/// general case: arbitrary C++ return type
template <typename CALLABLE, typename FOLLOWUP, typename RETURNTYPE>
struct WorkQueue::MakeReplyLambda
{
auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
{
// Call the callable in any case -- but to minimize
// copying the result, immediately bind it into the reply
// lambda. The reply lambda also binds the original
// callback, so that when we, the originating WorkQueue,
// finally receive and process the reply lambda, we'll
// call the bound callback with the bound result -- on the
// same thread that originally called postTo().
return
[result = std::forward<CALLABLE>(callable)(),
callback = std::move(callback)]
()
{ callback(std::move(result)); };
}
};
/// specialize for CALLABLE returning void
template <typename CALLABLE, typename FOLLOWUP>
struct WorkQueue::MakeReplyLambda<CALLABLE, FOLLOWUP, void>
{
auto operator()(CALLABLE&& callable, FOLLOWUP&& callback)
{
// Call the callable, which produces no result.
std::forward<CALLABLE>(callable)();
// Our completion callback is simply the caller's callback.
return std::move(callback);
}
};
template <typename CALLABLE, typename FOLLOWUP>
auto WorkQueue::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback)
{
return MakeReplyLambda<CALLABLE, FOLLOWUP,
decltype(std::forward<CALLABLE>(callable)())>()
(std::move(callable), std::move(callback));
}
template <typename CALLABLE, typename FOLLOWUP>
bool WorkQueue::postTo(weak_t target,
const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback)
{
// We're being asked to post to the WorkQueue at target.
// target is a weak_ptr: have to lock it to check it.
auto tptr = target.lock();
if (! tptr)
// can't post() if the target WorkQueue has been destroyed
return false;
// Here we believe target WorkQueue still exists. Post to it a
// lambda that packages our callable, our callback and a weak_ptr
// to this originating WorkQueue.
tptr->post(
time,
[reply = super::getWeak(),
callable = std::move(callable),
callback = std::move(callback)]
()
{
// Use postMaybe() below in case this originating WorkQueue
// has been closed or destroyed. Remember, the outer lambda is
// now running on a thread servicing the target WorkQueue, and
// real time has elapsed since postTo()'s tptr->post() call.
try
{
// Make a reply lambda to repost to THIS WorkQueue.
// Delegate to makeReplyLambda() so we can partially
// specialize on void return.
postMaybe(reply, makeReplyLambda(std::move(callable), std::move(callback)));
}
catch (...)
{
// Either variant of makeReplyLambda() is responsible for
// calling the caller's callable. If that throws, return
// the exception to the originating thread.
postMaybe(
reply,
// Bind the current exception to transport back to the
// originating WorkQueue. Once there, rethrow it.
[exc = std::current_exception()](){ std::rethrow_exception(exc); });
}
});
// looks like we were able to post()
return true;
}
template <typename CALLABLE>
bool WorkQueue::postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable)
{
// target is a weak_ptr: have to lock it to check it
auto tptr = target.lock();
if (tptr)
{
try
{
tptr->post(time, std::forward<CALLABLE>(callable));
// we were able to post()
return true;
}
catch (const Closed&)
{
// target WorkQueue still exists, but is Closed
}
}
// either target no longer exists, or its WorkQueue is Closed
return false;
}
/// general case: arbitrary C++ return type
template <typename CALLABLE, typename RETURNTYPE>
struct WorkQueue::WaitForResult
{
auto operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
{
LLCoros::Promise<RETURNTYPE> promise;
self->post(
time,
// We dare to bind a reference to Promise because it's
// specifically designed for cross-thread communication.
[&promise, callable = std::move(callable)]()
{
try
{
// call the caller's callable and trigger promise with result
promise.set_value(callable());
}
catch (...)
{
promise.set_exception(std::current_exception());
}
});
auto future{ LLCoros::getFuture(promise) };
// now, on the calling thread, wait for that result
LLCoros::TempStatus st("waiting for WorkQueue::waitForResult()");
return future.get();
}
};
/// specialize for CALLABLE returning void
template <typename CALLABLE>
struct WorkQueue::WaitForResult<CALLABLE, void>
{
void operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable)
{
LLCoros::Promise<void> promise;
self->post(
time,
// &promise is designed for cross-thread access
[&promise, callable = std::move(callable)]()
{
try
{
callable();
promise.set_value();
}
catch (...)
{
promise.set_exception(std::current_exception());
}
});
auto future{ LLCoros::getFuture(promise) };
// block until set_value()
LLCoros::TempStatus st("waiting for void WorkQueue::waitForResult()");
future.get();
}
};
template <typename CALLABLE>
auto WorkQueue::waitForResult(const TimePoint& time, CALLABLE&& callable)
{
checkCoroutine("waitForResult()");
// derive callable's return type so we can specialize for void
return WaitForResult<CALLABLE, decltype(std::forward<CALLABLE>(callable)())>()
(this, time, std::forward<CALLABLE>(callable));
}
} // namespace LL
#endif /* ! defined(LL_WORKQUEUE_H) */

View File

@ -172,19 +172,31 @@ BOOL is_little_endian()
return (*c == 0x78) ;
}
LLImageGLThread* LLImageGLThread::sInstance = nullptr;
//static
void LLImageGL::initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha /* = false */)
{
LL_PROFILE_ZONE_SCOPED;
sSkipAnalyzeAlpha = skip_analyze_alpha;
LLImageGLThread::createInstance(window);
LLImageGLThread::sInstance = new LLImageGLThread(window);
LLImageGLThread::sInstance->start();
}
//static
void LLImageGL::updateClass()
{
LL_PROFILE_ZONE_SCOPED;
LLImageGLThread::sInstance->executeCallbacks();
}
//static
void LLImageGL::cleanupClass()
{
LL_PROFILE_ZONE_SCOPED;
LLImageGLThread::deleteSingleton();
LLImageGLThread::sInstance->mFunctionQueue.close();
delete LLImageGLThread::sInstance;
LLImageGLThread::sInstance = nullptr;
}
//static
@ -492,9 +504,6 @@ void LLImageGL::init(BOOL usemipmaps)
#endif
mCategory = -1;
// Sometimes we have to post work for the main thread.
mMainQueue = LL::WorkQueue::getInstance("mainloop");
}
void LLImageGL::cleanup()
@ -1527,7 +1536,8 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
}
//if we're on the image loading thread, be sure to delete old_texname and update mTexName on the main thread
if (! on_main_thread())
if (LLImageGLThread::sInstance != nullptr &&
LLThread::currentID() == LLImageGLThread::sInstance->getID())
{
{
LL_PROFILE_ZONE_NAMED("cglt - sync");
@ -1544,9 +1554,7 @@ BOOL LLImageGL::createGLTexture(S32 discard_level, const U8* data_in, BOOL data_
}
ref();
LL::WorkQueue::postMaybe(
mMainQueue,
[=]()
LLImageGLThread::sInstance->postCallback([=]()
{
LL_PROFILE_ZONE_NAMED("cglt - delete callback");
if (old_texname != 0)
@ -2251,24 +2259,73 @@ void LLImageGL::resetCurTexSizebar()
*/
LLImageGLThread::LLImageGLThread(LLWindow* window)
// We want exactly one thread, but a very large capacity: we never want
// anyone, especially inner-loop render code, to have to block on post()
// because we're full.
: ThreadPool("LLImageGL", 1, 1024*1024)
, mWindow(window)
: LLThread("LLImageGL"), mWindow(window)
{
mFinished = false;
mContext = mWindow->createSharedContext();
}
// post a function to be executed on the LLImageGL background thread
bool LLImageGLThread::post(const std::function<void()>& func)
{
try
{
mFunctionQueue.post(func);
}
catch (LLThreadSafeQueueInterrupt e)
{
return false;
}
return true;
}
//post a callback to be executed on the main thread
bool LLImageGLThread::postCallback(const std::function<void()>& callback)
{
try
{
if (!mCallbackQueue.tryPost(callback))
{
mPendingCallbackQ.push(callback);
}
}
catch (LLThreadSafeQueueInterrupt e)
{
//thread is closing, drop request
return false;
}
return true;
}
void LLImageGLThread::executeCallbacks()
{
LL_PROFILE_ZONE_SCOPED;
//executed from main thread
mCallbackQueue.runPending();
while (!mPendingCallbackQ.empty())
{
if (mCallbackQueue.tryPost(mPendingCallbackQ.front()))
{
mPendingCallbackQ.pop();
}
else
{
break;
}
}
}
void LLImageGLThread::run()
{
// We must perform setup on this thread before actually servicing our
// WorkQueue, likewise cleanup afterwards.
mWindow->makeContextCurrent(mContext);
gGL.init();
ThreadPool::run();
mFunctionQueue.runUntilClose();
gGL.shutdown();
mWindow->destroySharedContext(mContext);
}

View File

@ -37,7 +37,6 @@
#include "llunits.h"
#include "llthreadsafequeue.h"
#include "llrender.h"
#include "threadpool.h"
#include "workqueue.h"
class LLTextureAtlas ;
@ -199,7 +198,6 @@ private:
void freePickMask();
LLPointer<LLImageRaw> mSaveData; // used for destroyGL/restoreGL
LL::WorkQueue::weak_t mMainQueue;
U8* mPickMask; //downsampled bitmap approximation of alpha channel. NULL if no alpha channel
U16 mPickMaskWidth;
U16 mPickMaskHeight;
@ -273,6 +271,7 @@ public:
public:
static void initClass(LLWindow* window, S32 num_catagories, BOOL skip_analyze_alpha = false);
static void updateClass();
static void cleanupClass() ;
private:
@ -308,24 +307,34 @@ public:
};
class LLImageGLThread : public LLSimpleton<LLImageGLThread>, LL::ThreadPool
class LLImageGLThread : public LLThread
{
public:
LLImageGLThread(LLWindow* window);
// post a function to be executed on the LLImageGL background thread
template <typename CALLABLE>
bool post(CALLABLE&& func)
{
return getQueue().postIfOpen(std::forward<CALLABLE>(func));
}
bool post(const std::function<void()>& func);
//post a callback to be executed on the main thread
bool postCallback(const std::function<void()>& callback);
void executeCallbacks();
void run() override;
private:
// Work Queue for background thread
LL::WorkQueue mFunctionQueue;
// Work Queue for main thread (run from updateClass)
LL::WorkQueue mCallbackQueue;
LLWindow* mWindow;
void* mContext;
LLAtomicBool mFinished;
std::queue<std::function<void()>> mPendingCallbackQ;
static LLImageGLThread* sInstance;
};

View File

@ -308,10 +308,6 @@ if(LL_TESTS)
${BOOST_FIBER_LIBRARY} ${BOOST_CONTEXT_LIBRARY} ${BOOST_SYSTEM_LIBRARY}
${WINDOWS_LIBRARIES})
if(NOT LINUX)
if(WINDOWS)
LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "imm32;${test_libs}")
else(WINDOWS)
LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}")
endif(WINDOWS)
LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}")
endif(NOT LINUX)
endif(LL_TESTS)

View File

@ -28,6 +28,8 @@
#include "llui.h"
#include "llleap.h"
LLViewerEventRecorder* LLSimpleton<LLViewerEventRecorder>::sInstance = nullptr;
LLViewerEventRecorder::LLViewerEventRecorder() {
clear(UNDEFINED);

View File

@ -55,8 +55,6 @@
#include <shellapi.h>
#include <fstream>
#include <Imm.h>
#include <future>
#include <utility> // std::pair
// Require DirectInput version 8
#define DIRECTINPUT_VERSION 0x0800
@ -176,19 +174,23 @@ DWORD LLWindowWin32::sWinIMESentenceMode = IME_SMODE_AUTOMATIC;
LLCoordWindow LLWindowWin32::sWinIMEWindowPosition(-1,-1);
// The following class LLWinImm delegates Windows IMM APIs.
// It was originally introduced to support US Windows XP, on which we needed
// to dynamically load IMM32.DLL and use GetProcAddress to resolve its entry
// points. Now that that's moot, we retain this wrapper only for hooks for
// metrics.
// We need this because some language versions of Windows,
// e.g., US version of Windows XP, doesn't install IMM32.DLL
// as a default, and we can't link against imm32.lib statically.
// I believe DLL loading of this type is best suited to do
// in a static initialization of a class. What I'm not sure is
// whether it follows the Linden Conding Standard...
// See http://wiki.secondlife.com/wiki/Coding_standards#Static_Members
class LLWinImm
{
public:
static bool isAvailable() { return true; }
static bool isAvailable() { return sTheInstance.mHImmDll != NULL; }
public:
// Wrappers for IMM API.
static BOOL isIME(HKL hkl);
static HWND getDefaultIMEWnd(HWND hwnd);
static HIMC getContext(HWND hwnd);
static BOOL releaseContext(HWND hwnd, HIMC himc);
static BOOL getOpenStatus(HIMC himc);
@ -202,96 +204,236 @@ public:
static BOOL setCompositionFont(HIMC himc, LPLOGFONTW logfont);
static BOOL setCandidateWindow(HIMC himc, LPCANDIDATEFORM candidate_form);
static BOOL notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value);
private:
LLWinImm();
~LLWinImm();
private:
// Pointers to IMM API.
BOOL (WINAPI *mImmIsIME)(HKL);
HWND (WINAPI *mImmGetDefaultIMEWnd)(HWND);
HIMC (WINAPI *mImmGetContext)(HWND);
BOOL (WINAPI *mImmReleaseContext)(HWND, HIMC);
BOOL (WINAPI *mImmGetOpenStatus)(HIMC);
BOOL (WINAPI *mImmSetOpenStatus)(HIMC, BOOL);
BOOL (WINAPI *mImmGetConversionStatus)(HIMC, LPDWORD, LPDWORD);
BOOL (WINAPI *mImmSetConversionStatus)(HIMC, DWORD, DWORD);
BOOL (WINAPI *mImmGetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM);
BOOL (WINAPI *mImmSetCompostitionWindow)(HIMC, LPCOMPOSITIONFORM);
LONG (WINAPI *mImmGetCompositionString)(HIMC, DWORD, LPVOID, DWORD);
BOOL (WINAPI *mImmSetCompositionString)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD);
BOOL (WINAPI *mImmSetCompositionFont)(HIMC, LPLOGFONTW);
BOOL (WINAPI *mImmSetCandidateWindow)(HIMC, LPCANDIDATEFORM);
BOOL (WINAPI *mImmNotifyIME)(HIMC, DWORD, DWORD, DWORD);
private:
HMODULE mHImmDll;
static LLWinImm sTheInstance;
};
LLWinImm LLWinImm::sTheInstance;
LLWinImm::LLWinImm() : mHImmDll(NULL)
{
// Check system metrics
if ( !GetSystemMetrics( SM_IMMENABLED ) )
return;
mHImmDll = LoadLibraryA("Imm32");
if (mHImmDll != NULL)
{
mImmIsIME = (BOOL (WINAPI *)(HKL)) GetProcAddress(mHImmDll, "ImmIsIME");
mImmGetDefaultIMEWnd = (HWND (WINAPI *)(HWND)) GetProcAddress(mHImmDll, "ImmGetDefaultIMEWnd");
mImmGetContext = (HIMC (WINAPI *)(HWND)) GetProcAddress(mHImmDll, "ImmGetContext");
mImmReleaseContext = (BOOL (WINAPI *)(HWND, HIMC)) GetProcAddress(mHImmDll, "ImmReleaseContext");
mImmGetOpenStatus = (BOOL (WINAPI *)(HIMC)) GetProcAddress(mHImmDll, "ImmGetOpenStatus");
mImmSetOpenStatus = (BOOL (WINAPI *)(HIMC, BOOL)) GetProcAddress(mHImmDll, "ImmSetOpenStatus");
mImmGetConversionStatus = (BOOL (WINAPI *)(HIMC, LPDWORD, LPDWORD)) GetProcAddress(mHImmDll, "ImmGetConversionStatus");
mImmSetConversionStatus = (BOOL (WINAPI *)(HIMC, DWORD, DWORD)) GetProcAddress(mHImmDll, "ImmSetConversionStatus");
mImmGetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM)) GetProcAddress(mHImmDll, "ImmGetCompositionWindow");
mImmSetCompostitionWindow = (BOOL (WINAPI *)(HIMC, LPCOMPOSITIONFORM)) GetProcAddress(mHImmDll, "ImmSetCompositionWindow");
mImmGetCompositionString= (LONG (WINAPI *)(HIMC, DWORD, LPVOID, DWORD)) GetProcAddress(mHImmDll, "ImmGetCompositionStringW");
mImmSetCompositionString= (BOOL (WINAPI *)(HIMC, DWORD, LPVOID, DWORD, LPVOID, DWORD)) GetProcAddress(mHImmDll, "ImmSetCompositionStringW");
mImmSetCompositionFont = (BOOL (WINAPI *)(HIMC, LPLOGFONTW)) GetProcAddress(mHImmDll, "ImmSetCompositionFontW");
mImmSetCandidateWindow = (BOOL (WINAPI *)(HIMC, LPCANDIDATEFORM)) GetProcAddress(mHImmDll, "ImmSetCandidateWindow");
mImmNotifyIME = (BOOL (WINAPI *)(HIMC, DWORD, DWORD, DWORD)) GetProcAddress(mHImmDll, "ImmNotifyIME");
if (mImmIsIME == NULL ||
mImmGetDefaultIMEWnd == NULL ||
mImmGetContext == NULL ||
mImmReleaseContext == NULL ||
mImmGetOpenStatus == NULL ||
mImmSetOpenStatus == NULL ||
mImmGetConversionStatus == NULL ||
mImmSetConversionStatus == NULL ||
mImmGetCompostitionWindow == NULL ||
mImmSetCompostitionWindow == NULL ||
mImmGetCompositionString == NULL ||
mImmSetCompositionString == NULL ||
mImmSetCompositionFont == NULL ||
mImmSetCandidateWindow == NULL ||
mImmNotifyIME == NULL)
{
// If any of the above API entires are not found, we can't use IMM API.
// So, turn off the IMM support. We should log some warning message in
// the case, since it is very unusual; these APIs are available from
// the beginning, and all versions of IMM32.DLL should have them all.
// Unfortunately, this code may be executed before initialization of
// the logging channel (LL_WARNS()), and we can't do it here... Yes, this
// is one of disadvantages to use static constraction to DLL loading.
FreeLibrary(mHImmDll);
mHImmDll = NULL;
// If we unload the library, make sure all the function pointers are cleared
mImmIsIME = NULL;
mImmGetDefaultIMEWnd = NULL;
mImmGetContext = NULL;
mImmReleaseContext = NULL;
mImmGetOpenStatus = NULL;
mImmSetOpenStatus = NULL;
mImmGetConversionStatus = NULL;
mImmSetConversionStatus = NULL;
mImmGetCompostitionWindow = NULL;
mImmSetCompostitionWindow = NULL;
mImmGetCompositionString = NULL;
mImmSetCompositionString = NULL;
mImmSetCompositionFont = NULL;
mImmSetCandidateWindow = NULL;
mImmNotifyIME = NULL;
}
}
}
// static
BOOL LLWinImm::isIME(HKL hkl)
{
return ImmIsIME(hkl);
if ( sTheInstance.mImmIsIME )
return sTheInstance.mImmIsIME(hkl);
return FALSE;
}
// static
HIMC LLWinImm::getContext(HWND hwnd)
{
return ImmGetContext(hwnd);
if ( sTheInstance.mImmGetContext )
return sTheInstance.mImmGetContext(hwnd);
return 0;
}
//static
BOOL LLWinImm::releaseContext(HWND hwnd, HIMC himc)
{
return ImmReleaseContext(hwnd, himc);
if ( sTheInstance.mImmIsIME )
return sTheInstance.mImmReleaseContext(hwnd, himc);
return FALSE;
}
// static
BOOL LLWinImm::getOpenStatus(HIMC himc)
{
return ImmGetOpenStatus(himc);
if ( sTheInstance.mImmGetOpenStatus )
return sTheInstance.mImmGetOpenStatus(himc);
return FALSE;
}
// static
BOOL LLWinImm::setOpenStatus(HIMC himc, BOOL status)
{
return ImmSetOpenStatus(himc, status);
if ( sTheInstance.mImmSetOpenStatus )
return sTheInstance.mImmSetOpenStatus(himc, status);
return FALSE;
}
// static
BOOL LLWinImm::getConversionStatus(HIMC himc, LPDWORD conversion, LPDWORD sentence)
{
return ImmGetConversionStatus(himc, conversion, sentence);
if ( sTheInstance.mImmGetConversionStatus )
return sTheInstance.mImmGetConversionStatus(himc, conversion, sentence);
return FALSE;
}
// static
BOOL LLWinImm::setConversionStatus(HIMC himc, DWORD conversion, DWORD sentence)
{
return ImmSetConversionStatus(himc, conversion, sentence);
if ( sTheInstance.mImmSetConversionStatus )
return sTheInstance.mImmSetConversionStatus(himc, conversion, sentence);
return FALSE;
}
// static
BOOL LLWinImm::getCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form)
{
return ImmGetCompositionWindow(himc, form);
if ( sTheInstance.mImmGetCompostitionWindow )
return sTheInstance.mImmGetCompostitionWindow(himc, form);
return FALSE;
}
// static
BOOL LLWinImm::setCompositionWindow(HIMC himc, LPCOMPOSITIONFORM form)
{
return ImmSetCompositionWindow(himc, form);
if ( sTheInstance.mImmSetCompostitionWindow )
return sTheInstance.mImmSetCompostitionWindow(himc, form);
return FALSE;
}
// static
LONG LLWinImm::getCompositionString(HIMC himc, DWORD index, LPVOID data, DWORD length)
{
return ImmGetCompositionString(himc, index, data, length);
if ( sTheInstance.mImmGetCompositionString )
return sTheInstance.mImmGetCompositionString(himc, index, data, length);
return FALSE;
}
// static
BOOL LLWinImm::setCompositionString(HIMC himc, DWORD index, LPVOID pComp, DWORD compLength, LPVOID pRead, DWORD readLength)
{
return ImmSetCompositionString(himc, index, pComp, compLength, pRead, readLength);
if ( sTheInstance.mImmSetCompositionString )
return sTheInstance.mImmSetCompositionString(himc, index, pComp, compLength, pRead, readLength);
return FALSE;
}
// static
BOOL LLWinImm::setCompositionFont(HIMC himc, LPLOGFONTW pFont)
{
return ImmSetCompositionFont(himc, pFont);
if ( sTheInstance.mImmSetCompositionFont )
return sTheInstance.mImmSetCompositionFont(himc, pFont);
return FALSE;
}
// static
BOOL LLWinImm::setCandidateWindow(HIMC himc, LPCANDIDATEFORM form)
{
return ImmSetCandidateWindow(himc, form);
if ( sTheInstance.mImmSetCandidateWindow )
return sTheInstance.mImmSetCandidateWindow(himc, form);
return FALSE;
}
// static
BOOL LLWinImm::notifyIME(HIMC himc, DWORD action, DWORD index, DWORD value)
{
return ImmNotifyIME(himc, action, index, value);
if ( sTheInstance.mImmNotifyIME )
return sTheInstance.mImmNotifyIME(himc, action, index, value);
return FALSE;
}
// ----------------------------------------------------------------------------------------
LLWinImm::~LLWinImm()
{
if (mHImmDll != NULL)
{
FreeLibrary(mHImmDll);
mHImmDll = NULL;
}
}
class LLMonitorInfo
{
public:
@ -326,32 +468,6 @@ private:
static LLMonitorInfo sMonitorInfo;
// Thread that owns the Window Handle
// This whole struct is private to LLWindowWin32, which needs to mess with its
// members, which is why it's a struct rather than a class. In effect, we make
// the containing class a friend.
struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool
{
static const int MAX_QUEUE_SIZE = 2048;
LLThreadSafeQueue<MSG> mMessageQueue;
LLWindowWin32Thread();
void run() override;
template <typename CALLABLE>
void post(CALLABLE&& func)
{
getQueue().post(std::forward<CALLABLE>(func));
}
// call PeekMessage and pull enqueue messages for later processing
void gatherInput();
HWND mWindowHandle = NULL;
HDC mhDC = 0;
};
LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
const std::string& title, const std::string& name, S32 x, S32 y, S32 width,
@ -363,7 +479,8 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
: LLWindow(callbacks, fullscreen, flags)
{
sMainThreadId = LLThread::currentID();
mWindowThread = new LLWindowWin32Thread();
mWindowThread = new LLWindowWin32Thread(this);
mWindowThread->start();
//MAINT-516 -- force a load of opengl32.dll just in case windows went sideways
LoadLibrary(L"opengl32.dll");
@ -434,6 +551,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
// Make an instance of our window then define the window class
mhInstance = GetModuleHandle(NULL);
mWndProc = NULL;
// Init Direct Input - needed for joystick / Spacemouse
@ -857,13 +975,17 @@ void LLWindowWin32::close()
// Something killed the window while we were busy destroying gl or handle somehow got broken
LL_WARNS("Window") << "Failed to destroy Window, invalid handle!" << LL_ENDL;
}
mWindowHandle = NULL;
mWindowThread->mFinished = true;
});
// Even though the above lambda might not yet have run, we've already
// bound mWindowHandle into it by value, which should suffice for the
// operations we're asking. That's the last time WE should touch it.
mWindowHandle = NULL;
mWindowThread->close();
while (!mWindowThread->isStopped())
{
//nudge window thread
PostMessage(mWindowHandle, WM_USER + 0x0017, 0xB0B0, 0x1337);
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
BOOL LLWindowWin32::isValid()
@ -1156,7 +1278,51 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen& size, BO
<< " Fullscreen: " << mFullscreen
<< LL_ENDL;
recreateWindow(window_rect, dw_ex_style, dw_style);
auto oldHandle = mWindowHandle;
//zero out mWindowHandle and mhDC before destroying window so window thread falls back to peekmessage
mWindowHandle = 0;
mhDC = 0;
if (oldHandle && !destroy_window_handler(oldHandle))
{
LL_WARNS("Window") << "Failed to properly close window before recreating it!" << LL_ENDL;
}
mWindowHandle = NULL;
mhDC = 0;
mWindowThread->post(
[this, window_rect, dw_ex_style, dw_style]()
{
mWindowHandle = CreateWindowEx(dw_ex_style,
mWindowClassName,
mWindowTitle,
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
window_rect.left, // x pos
window_rect.top, // y pos
window_rect.right - window_rect.left, // width
window_rect.bottom - window_rect.top, // height
NULL,
NULL,
mhInstance,
NULL);
if (mWindowHandle)
{
mhDC = GetDC(mWindowHandle);
}
}
);
// HACK wait for above handle to become populated
// TODO: use a future
int count = 1024;
while (!mhDC && count > 0)
{
Sleep(10);
--count;
}
if (mWindowHandle)
{
@ -1484,7 +1650,48 @@ const S32 max_format = (S32)num_formats - 1;
mhDC = 0; // Zero The Device Context
}
recreateWindow(window_rect, dw_ex_style, dw_style);
auto oldHandle = mWindowHandle;
mWindowHandle = NULL;
mhDC = 0;
// Destroy The Window
if (oldHandle && !destroy_window_handler(oldHandle))
{
LL_WARNS("Window") << "Failed to properly close window!" << LL_ENDL;
}
mWindowThread->post(
[this, window_rect, dw_ex_style, dw_style]()
{
mWindowHandle = CreateWindowEx(dw_ex_style,
mWindowClassName,
mWindowTitle,
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
window_rect.left, // x pos
window_rect.top, // y pos
window_rect.right - window_rect.left, // width
window_rect.bottom - window_rect.top, // height
NULL,
NULL,
mhInstance,
NULL);
if (mWindowHandle)
{
mhDC = GetDC(mWindowHandle);
}
}
);
// HACK wait for above handle to become populated
// TODO: use a future
int count = 1024;
while (!mhDC && count > 0)
{
PostMessage(oldHandle, WM_USER + 8, 0x1717, 0x3b3b);
Sleep(10);
--count;
}
if (mWindowHandle)
{
@ -1621,64 +1828,6 @@ const S32 max_format = (S32)num_formats - 1;
return TRUE;
}
void LLWindowWin32::recreateWindow(RECT window_rect, DWORD dw_ex_style, DWORD dw_style)
{
auto oldHandle = mWindowHandle;
// zero out mWindowHandle and mhDC before destroying window so window
// thread falls back to peekmessage
mWindowHandle = 0;
mhDC = 0;
if (oldHandle && !destroy_window_handler(oldHandle))
{
LL_WARNS("Window") << "Failed to properly close window before recreating it!" << LL_ENDL;
}
std::promise<std::pair<HWND, HDC>> promise;
mWindowThread->post(
[this, window_rect, dw_ex_style, dw_style, &promise]()
{
auto handle = CreateWindowEx(dw_ex_style,
mWindowClassName,
mWindowTitle,
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | dw_style,
window_rect.left, // x pos
window_rect.top, // y pos
window_rect.right - window_rect.left, // width
window_rect.bottom - window_rect.top, // height
NULL,
NULL,
mhInstance,
NULL);
if (! handle)
{
// Failed to create window: clear the variables. This
// assignment is valid because we're running on mWindowThread.
mWindowThread->mWindowHandle = NULL;
mWindowThread->mhDC = 0;
}
else
{
// Update mWindowThread's own mWindowHandle and mhDC.
mWindowThread->mWindowHandle = handle;
mWindowThread->mhDC = GetDC(handle);
}
// It's important to wake up the future either way.
promise.set_value(std::make_pair(mWindowThread->mWindowHandle, mWindowThread->mhDC));
}
);
auto future = promise.get_future();
// This blocks until mWindowThread processes CreateWindowEx() and calls
// promise.set_value().
auto pair = future.get();
mWindowHandle = pair.first;
mhDC = pair.second;
}
void* LLWindowWin32::createSharedContext()
{
S32 attribs[] =
@ -2032,14 +2181,12 @@ void LLWindowWin32::gatherInput()
}
if (mWindowThread->getQueue().size())
if (mWindowThread->mFunctionQueue.size() > 0)
{
LL_PROFILE_ZONE_NAMED("gi - PostMessage");
if (mWindowHandle)
{
// post a nonsense user message to wake up the Window Thread in
// case any functions are pending and no windows events came
// through this frame
{ // post a nonsense user message to wake up the Window Thread in case any functions are pending
// and no windows events came through this frame
PostMessage(mWindowHandle, WM_USER + 0x0017, 0xB0B0, 0x1337);
}
}
@ -2129,6 +2276,17 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
if (NULL != window_imp)
{
// Has user provided their own window callback?
if (NULL != window_imp->mWndProc)
{
LL_PROFILE_ZONE_NAMED("mwp - WndProc");
if (!window_imp->mWndProc(h_wnd, u_msg, w_param, l_param))
{
// user has handled window message
return 0;
}
}
// Juggle to make sure we can get negative positions for when
// mouse is outside window.
LLCoordWindow window_coord((S32)(S16)LOWORD(l_param), (S32)(S16)HIWORD(l_param));
@ -4409,32 +4567,35 @@ std::vector<std::string> LLWindowWin32::getDynamicFallbackFontList()
#endif // LL_WINDOWS
inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread()
: ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE)
inline LLWindowWin32Thread::LLWindowWin32Thread(LLWindowWin32* window)
: LLThread("Window Thread"),
mWindow(window),
mFunctionQueue(MAX_QUEUE_SIZE)
{
}
void LLWindowWin32::LLWindowWin32Thread::run()
inline void LLWindowWin32Thread::run()
{
sWindowThreadId = std::this_thread::get_id();
while (! getQueue().done())
sWindowThreadId = getID();
while (!mFinished)
{
LL_PROFILE_ZONE_SCOPED;
if (mWindowHandle != 0)
if (mWindow && mWindow->mWindowHandle != 0)
{
MSG msg;
BOOL status;
if (mhDC == 0)
if (mWindow->mhDC == 0)
{
LL_PROFILE_ZONE_NAMED("w32t - PeekMessage");
status = PeekMessage(&msg, mWindowHandle, 0, 0, PM_REMOVE);
status = PeekMessage(&msg, mWindow->mWindowHandle, 0, 0, PM_REMOVE);
}
else
{
LL_PROFILE_ZONE_NAMED("w32t - GetMessage");
status = GetMessage(&msg, mWindowHandle, 0, 0);
status = GetMessage(&msg, mWindow->mWindowHandle, 0, 0);
}
if (status > 0)
{
@ -4448,7 +4609,11 @@ void LLWindowWin32::LLWindowWin32Thread::run()
{
LL_PROFILE_ZONE_NAMED("w32t - Function Queue");
//process any pending functions
getQueue().runPending();
std::function<void()> curFunc;
while (mFunctionQueue.tryPopBack(curFunc))
{
curFunc();
}
}
#if 0
@ -4460,6 +4625,11 @@ void LLWindowWin32::LLWindowWin32Thread::run()
}
}
void LLWindowWin32Thread::post(const std::function<void()>& func)
{
mFunctionQueue.pushFront(func);
}
void LLWindowWin32::post(const std::function<void()>& func)
{
mFunctionQueue.pushFront(func);

View File

@ -36,12 +36,44 @@
#include "llthread.h"
#include "llthreadsafequeue.h"
#include "llmutex.h"
#include "workqueue.h"
// Hack for async host by name
#define LL_WM_HOST_RESOLVED (WM_APP + 1)
typedef void (*LLW32MsgCallback)(const MSG &msg);
class LLWindowWin32;
// Thread that owns the Window Handle
class LLWindowWin32Thread : public LLThread
{
public:
class Message
{
public:
LRESULT mMsg;
};
static const int MAX_QUEUE_SIZE = 2048;
LLThreadSafeQueue<MSG> mMessageQueue;
LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
bool mFinished = false;
LLWindowWin32Thread(LLWindowWin32* window);
void run() override;
void post(const std::function<void()>& func);
private:
// call PeekMessage and pull enqueue messages for later processing
void gatherInput();
LLWindowWin32* mWindow = nullptr;
};
class LLWindowWin32 : public LLWindow
{
public:
@ -186,6 +218,7 @@ protected:
HGLRC mhRC = 0; // OpenGL rendering context
HDC mhDC = 0; // Windows Device context handle
HINSTANCE mhInstance; // handle to application instance
WNDPROC mWndProc; // user-installable window proc
RECT mOldMouseClip; // Screen rect to which the mouse cursor was globally constrained before we changed it in clipMouse()
WPARAM mLastSizeWParam;
F32 mOverrideAspectRatio;
@ -237,15 +270,14 @@ protected:
BOOL mMouseVanish;
struct LLWindowWin32Thread;
LLWindowWin32Thread* mWindowThread = nullptr;
LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
LLThreadSafeQueue<std::function<void()>> mMouseQueue;
void post(const std::function<void()>& func);
void postMouseButtonEvent(const std::function<void()>& func);
void recreateWindow(RECT window_rect, DWORD dw_ex_style, DWORD dw_style);
LLWindowWin32Thread* mWindowThread = nullptr;
LLThreadSafeQueue<std::function<void()>> mFunctionQueue;
LLThreadSafeQueue<std::function<void()>> mMouseQueue;
void post(const std::function<void()>& func);
void postMouseButtonEvent(const std::function<void()>& func);
friend class LLWindowManager;
friend class LLWindowWin32Thread;
};
class LLSplashScreenWin32 : public LLSplashScreen

View File

@ -393,6 +393,7 @@ set(viewer_SOURCE_FILES
llloginhandler.cpp
lllogininstance.cpp
llmachineid.cpp
llmainlooprepeater.cpp
llmanip.cpp
llmaniprotate.cpp
llmanipscale.cpp
@ -1031,6 +1032,7 @@ set(viewer_HEADER_FILES
llloginhandler.h
lllogininstance.h
llmachineid.h
llmainlooprepeater.h
llmanip.h
llmaniprotate.h
llmanipscale.h
@ -1602,7 +1604,6 @@ if (WINDOWS)
${WINDOWS_LIBRARIES}
comdlg32
dxguid
imm32
kernel32
odbc32
odbccp32

View File

@ -3858,17 +3858,6 @@
<key>Value</key>
<integer>1</integer>
</map>
<key>MainWorkTime</key>
<map>
<key>Comment</key>
<string>Max time per frame devoted to mainloop work queue (in milliseconds)</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>F32</string>
<key>Value</key>
<real>0.1</real>
</map>
<key>QueueInventoryFetchTimeout</key>
<map>
<key>Comment</key>
@ -12674,20 +12663,6 @@
<key>Value</key>
<integer>50</integer>
</map>
<key>ThreadPoolSizes</key>
<map>
<key>Comment</key>
<string>Map of size overrides for specific thread pools.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>LLSD</string>
<key>Value</key>
<map>
<key>General</key>
<integer>4</integer>
</map>
</map>
<key>ThrottleBandwidthKBPS</key>
<map>
<key>Comment</key>

View File

@ -233,12 +233,11 @@
#include "llavatariconctrl.h"
#include "llgroupiconctrl.h"
#include "llviewerassetstats.h"
#include "workqueue.h"
using namespace LL;
// Include for security api initialization
#include "llsecapi.h"
#include "llmachineid.h"
#include "llmainlooprepeater.h"
#include "llcleanup.h"
#include "llcoproceduremanager.h"
@ -367,10 +366,6 @@ BOOL gLogoutInProgress = FALSE;
BOOL gSimulateMemLeak = FALSE;
// We don't want anyone, especially threads working on the graphics pipeline,
// to have to block due to this WorkQueue being full.
WorkQueue gMainloopWork("mainloop", 1024*1024);
////////////////////////////////////////////////////////////
// Internal globals... that should be removed.
static std::string gArgs;
@ -386,6 +381,42 @@ static std::string gLaunchFileOnQuit;
// Used on Win32 for other apps to identify our window (eg, win_setup)
const char* const VIEWER_WINDOW_CLASSNAME = "Second Life";
//-- LLDeferredTaskList ------------------------------------------------------
/**
* A list of deferred tasks.
*
* We sometimes need to defer execution of some code until the viewer gets idle,
* e.g. removing an inventory item from within notifyObservers() may not work out.
*
* Tasks added to this list will be executed in the next LLAppViewer::idle() iteration.
* All tasks are executed only once.
*/
class LLDeferredTaskList: public LLSingleton<LLDeferredTaskList>
{
LLSINGLETON_EMPTY_CTOR(LLDeferredTaskList);
LOG_CLASS(LLDeferredTaskList);
friend class LLAppViewer;
typedef boost::signals2::signal<void()> signal_t;
void addTask(const signal_t::slot_type& cb)
{
mSignal.connect(cb);
}
void run()
{
if (!mSignal.empty())
{
mSignal();
mSignal.disconnect_all_slots();
}
}
signal_t mSignal;
};
//----------------------------------------------------------------------------
// List of entries from strings.xml to always replace
@ -943,6 +974,9 @@ bool LLAppViewer::init()
}
LL_INFOS("InitInfo") << "Cache initialization is done." << LL_ENDL ;
// Initialize the repeater service.
LLMainLoopRepeater::instance().start();
// Initialize event recorder
LLViewerEventRecorder::createInstance();
@ -2158,6 +2192,8 @@ bool LLAppViewer::cleanup()
SUBSYSTEM_CLEANUP(LLProxy);
LLCore::LLHttp::cleanup();
LLMainLoopRepeater::instance().stop();
ll_close_fail_log();
LLError::LLCallStacks::cleanup();
@ -4452,7 +4488,7 @@ bool LLAppViewer::initCache()
void LLAppViewer::addOnIdleCallback(const boost::function<void()>& cb)
{
gMainloopWork.post(cb);
LLDeferredTaskList::instance().addTask(cb);
}
void LLAppViewer::loadKeyBindings()
@ -4850,6 +4886,7 @@ void LLAppViewer::idle()
LLNotificationsUI::LLToast::updateClass();
LLSmoothInterpolation::updateInterpolants();
LLMortician::updateClass();
LLImageGL::updateClass();
LLFilePickerThread::clearDead(); //calls LLFilePickerThread::notify()
LLDirPickerThread::clearDead();
F32 dt_raw = idle_timer.getElapsedTimeAndResetF32();
@ -5226,19 +5263,8 @@ void LLAppViewer::idle()
}
}
// Service the WorkQueue we use for replies from worker threads.
// Use function statics for the timeslice setting so we only have to fetch
// and convert MainWorkTime once.
static F32 MainWorkTimeRaw = gSavedSettings.getF32("MainWorkTime");
static F32Milliseconds MainWorkTimeMs(MainWorkTimeRaw);
// MainWorkTime is specified in fractional milliseconds, but std::chrono
// uses integer representations. What if we want less than a microsecond?
// Use nanoseconds. We're very sure we will never need to specify a
// MainWorkTime that would be larger than we could express in
// std::chrono::nanoseconds.
static std::chrono::nanoseconds MainWorkTimeNanoSec{
std::chrono::nanoseconds::rep(MainWorkTimeMs.value() * 1000000)};
gMainloopWork.runFor(MainWorkTimeNanoSec);
// Execute deferred tasks.
LLDeferredTaskList::instance().run();
// Handle shutdown process, for example,
// wait for floaters to close, send quit message,

View File

@ -824,6 +824,7 @@ std::string env_selection_to_string(LLEnvironment::EnvSelection_t sel)
#undef RTNENUM
}
LLEnvironment* LLSimpleton<LLEnvironment>::sInstance = nullptr;
//-------------------------------------------------------------------------
LLEnvironment::LLEnvironment():
mCloudScrollDelta(),

View File

@ -0,0 +1,88 @@
/**
* @file llmachineid.cpp
* @brief retrieves unique machine ids
*
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llapr.h"
#include "llevents.h"
#include "llmainlooprepeater.h"
// LLMainLoopRepeater
//-----------------------------------------------------------------------------
LLMainLoopRepeater::LLMainLoopRepeater(void):
mQueue(0)
{
; // No op.
}
void LLMainLoopRepeater::start(void)
{
if(mQueue != 0) return;
mQueue = new LLThreadSafeQueue<LLSD>(1024);
mMainLoopConnection = LLEventPumps::instance().
obtain("mainloop").listen(LLEventPump::inventName(), boost::bind(&LLMainLoopRepeater::onMainLoop, this, _1));
mRepeaterConnection = LLEventPumps::instance().
obtain("mainlooprepeater").listen(LLEventPump::inventName(), boost::bind(&LLMainLoopRepeater::onMessage, this, _1));
}
void LLMainLoopRepeater::stop(void)
{
mMainLoopConnection.release();
mRepeaterConnection.release();
delete mQueue;
mQueue = 0;
}
bool LLMainLoopRepeater::onMainLoop(LLSD const &)
{
LLSD message;
while(mQueue->tryPopBack(message)) {
std::string pump = message["pump"].asString();
if(pump.length() == 0 ) continue; // No pump.
LLEventPumps::instance().obtain(pump).post(message["payload"]);
}
return false;
}
bool LLMainLoopRepeater::onMessage(LLSD const & event)
{
try {
mQueue->pushFront(event);
} catch(LLThreadSafeQueueError & e) {
LL_WARNS() << "could not repeat message (" << e.what() << ")" <<
event.asString() << LL_ENDL;
}
return false;
}

View File

@ -0,0 +1,64 @@
/**
* @file llmainlooprepeater.h
* @brief a service for repeating messages on the main loop.
*
* $LicenseInfo:firstyear=2010&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLMAINLOOPREPEATER_H
#define LL_LLMAINLOOPREPEATER_H
#include "llsd.h"
#include "llthreadsafequeue.h"
//
// A service which creates the pump 'mainlooprepeater' to which any thread can
// post a message that will be re-posted on the main loop.
//
// The posted message should contain two map elements: pump and payload. The
// pump value is a string naming the pump to which the message should be
// re-posted. The payload value is what will be posted to the designated pump.
//
class LLMainLoopRepeater:
public LLSingleton<LLMainLoopRepeater>
{
LLSINGLETON(LLMainLoopRepeater);
public:
// Start the repeater service.
void start(void);
// Stop the repeater service.
void stop(void);
private:
LLTempBoundListener mMainLoopConnection;
LLTempBoundListener mRepeaterConnection;
LLThreadSafeQueue<LLSD> * mQueue;
bool onMainLoop(LLSD const &);
bool onMessage(LLSD const & event);
};
#endif

View File

@ -97,6 +97,8 @@
#include "llglheaders.h"
#include "llinventoryobserver.h"
LLSelectMgr* LLSimpleton<LLSelectMgr>::sInstance = nullptr;
LLViewerObject* getSelectedParentObject(LLViewerObject *object) ;
//
// Consts

View File

@ -205,9 +205,6 @@
#include "llstacktrace.h"
#include "threadpool.h"
#if LL_WINDOWS
#include "lldxhardware.h"
#endif
@ -304,20 +301,6 @@ void callback_cache_name(const LLUUID& id, const std::string& full_name, bool is
// local classes
//
void launchThreadPool()
{
LLSD poolSizes{ gSavedSettings.getLLSD("ThreadPoolSizes") };
LLSD sizeSpec{ poolSizes["General"] };
LLSD::Integer size{ sizeSpec.isInteger()? sizeSpec.asInteger() : 3 };
LL_DEBUGS("ThreadPool") << "Instantiating General pool with "
<< size << " threads" << LL_ENDL;
// Use a function-static ThreadPool: static duration, but instantiated
// only on demand.
// We don't want anyone, especially the main thread, to have to block
// due to this ThreadPool being full.
static LL::ThreadPool pool("General", size, 1024*1024);
}
void update_texture_fetch()
{
LLAppViewer::getTextureCache()->update(1); // unpauses the texture cache thread
@ -1506,9 +1489,6 @@ bool idle_startup()
gAgentCamera.resetCamera();
display_startup();
// start up the ThreadPool we'll use for textures et al.
launchThreadPool();
// Initialize global class data needed for surfaces (i.e. textures)
LL_DEBUGS("AppInit") << "Initializing sky..." << LL_ENDL;
// Initialize all of the viewer object classes for the first time (doing things like texture fetches.

View File

@ -54,6 +54,8 @@
// System includes
#include <iomanip> // for setprecision
LLViewerCamera* LLSimpleton<LLViewerCamera>::sInstance = nullptr;
LLTrace::CountStatHandle<> LLViewerCamera::sVelocityStat("camera_velocity");
LLTrace::CountStatHandle<> LLViewerCamera::sAngularVelocityStat("camera_angular_velocity");

View File

@ -679,9 +679,6 @@ void LLViewerTexture::init(bool firstinit)
mVolumeList[LLRender::LIGHT_TEX].clear();
mVolumeList[LLRender::SCULPT_TEX].clear();
mMainQueue = LL::WorkQueue::getInstance("mainloop");
mImageQueue = LL::WorkQueue::getInstance("LLImageGL");
}
//virtual
@ -1625,26 +1622,17 @@ void LLViewerFetchedTexture::scheduleCreateTexture()
{
mNeedsCreateTexture = TRUE;
#if LL_WINDOWS //flip to 0 to revert to single-threaded OpenGL texture uploads
auto mainq = mMainQueue.lock();
if (mainq)
{
mainq->postTo(
mImageQueue,
// work to be done on LLImageGL worker thread
[this]()
{
//actually create the texture on a background thread
createTexture();
},
// callback to be run on main thread
[this]()
{
//finalize on main thread
postCreateTexture();
unref();
});
}
else
if (!LLImageGLThread::sInstance->post([this]()
{
//actually create the texture on a background thread
createTexture();
LLImageGLThread::sInstance->postCallback([this]()
{
//finalize on main thread
postCreateTexture();
unref();
});
}))
#endif
{
gTextureList.mCreateTextureList.insert(this);

View File

@ -35,7 +35,6 @@
#include "llrender.h"
#include "llmetricperformancetester.h"
#include "httpcommon.h"
#include "workqueue.h"
#include <map>
#include <list>
@ -214,9 +213,6 @@ protected:
//do not use LLPointer here.
LLViewerMediaTexture* mParcelMedia ;
LL::WorkQueue::weak_t mMainQueue;
LL::WorkQueue::weak_t mImageQueue;
static F32 sTexelPixelRatio;
public:
static const U32 sCurrentFileVersion;

View File

@ -62,6 +62,8 @@
#include <cstring>
LLWorld* LLSimpleton<LLWorld>::sInstance = nullptr;
//
// Globals
//