From 25711066df80e789515c65ef05b05d1290e2191a Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Sat, 13 Apr 2013 21:22:03 +0200 Subject: [PATCH] Add prioritizedworkqueue, thread stuff, ui tweaks, small optimizations in file util --- Android.mk | 4 +- base/basictypes.h | 5 +- base/mutex.h | 88 ++++++++- file/file_util.cpp | 13 +- gfx/gl_lost_manager.cpp | 3 +- gfx/gl_lost_manager.h | 2 +- math/curves.cpp | 2 + native.vcxproj | 7 +- native.vcxproj.filters | 24 ++- thread/prioritizedworkqueue.cpp | 82 +++++++++ thread/prioritizedworkqueue.h | 51 ++++++ thread/thread.h | 310 ++++++++++++++++++++++++++++++++ {base => thread}/threadutil.cpp | 2 +- {base => thread}/threadutil.h | 18 ++ ui/screen.cpp | 3 - ui/screen.h | 5 +- ui/ui.cpp | 4 +- ui/ui.h | 4 +- 18 files changed, 587 insertions(+), 40 deletions(-) create mode 100644 thread/prioritizedworkqueue.cpp create mode 100644 thread/prioritizedworkqueue.h create mode 100644 thread/thread.h rename {base => thread}/threadutil.cpp (95%) rename {base => thread}/threadutil.h (64%) diff --git a/Android.mk b/Android.mk index e20ce9c897..29a7993e5c 100644 --- a/Android.mk +++ b/Android.mk @@ -41,6 +41,8 @@ LOCAL_SRC_FILES :=\ net/http_client.cpp \ net/resolve.cpp \ profiler/profiler.cpp \ + thread/threadutil.cpp \ + thread/prioritizedworkqueue.cpp \ gfx_es2/glsl_program.cpp \ gfx_es2/gl_state.cpp \ gfx_es2/draw_buffer.cpp.arm \ @@ -62,7 +64,7 @@ LOCAL_SRC_FILES :=\ util/text/utf8.cpp LOCAL_CFLAGS := -O3 -DGL_GLEXT_PROTOTYPES -DUSING_GLES2 -fsigned-char -fno-strict-aliasing -LOCAL_CPPFLAGS := -fno-exceptions -fno-rtti -std=gnu++0x +LOCAL_CPPFLAGS := -fno-exceptions -std=gnu++11 LOCAL_LDLIBS := -lz LOCAL_C_INCLUDES := $(LOCAL_PATH)/ext/libzip diff --git a/base/basictypes.h b/base/basictypes.h index fea1874486..32efa91cdd 100644 --- a/base/basictypes.h +++ b/base/basictypes.h @@ -1,5 +1,4 @@ -#ifndef _BASE_BASICTYPES -#define _BASE_BASICTYPES +#pragma once #include #include // for byte swapping @@ -92,5 +91,3 @@ inline uint64 swap64(const uint8* _pData) {return swap64(*(const uint64*)_pData) #ifndef TCHAR typedef char TCHAR; #endif - -#endif // _BASE_BASICTYPES diff --git a/base/mutex.h b/base/mutex.h index cae54dae77..ebd73df85b 100644 --- a/base/mutex.h +++ b/base/mutex.h @@ -1,19 +1,20 @@ #pragma once + // Simple cross platform mutex implementation. // Similar to the new C++11 api. + // Windows and pthreads implementations in one. +// TODO: Need to clean up these primitives and put them in a reasonable namespace. + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN +#define NOMINMAX #include // Zap stupid windows defines // Should move these somewhere clever. #undef p -#undef MIN -#undef MAX -#undef min -#undef max #undef DrawText #undef itoa @@ -125,10 +126,11 @@ public: void wait(recursive_mutex &mtx) { // broken #ifdef _WIN32 - //mtx.unlock(); + // This has to be horribly racy. + mtx.unlock(); WaitForSingleObject(event_, INFINITE); ResetEvent(event_); // necessary? - // mtx.lock(); + mtx.lock(); #else pthread_mutex_lock(&mtx.native_handle()); pthread_cond_wait(&event_, &mtx.native_handle()); @@ -167,3 +169,77 @@ private: pthread_cond_t event_; #endif }; + + +class condition_variable { +public: +#ifdef _WIN32 +#else +#endif + condition_variable() { +#ifdef _WIN32 + event_ = CreateEvent(0, FALSE, FALSE, 0); +#else + pthread_cond_init(&event_, NULL); +#endif + } + ~condition_variable() { +#ifdef _WIN32 + CloseHandle(event_); +#else + pthread_cond_destroy(&event_); +#endif + } + + void notify_one() { +#ifdef _WIN32 + SetEvent(event_); +#else + pthread_cond_signal(&event_); +#endif + } + + // notify_all is not really possible to implement with win32 events? + + void wait(recursive_mutex &mtx) { + // broken +#ifdef _WIN32 + // This has to be horribly racy. + mtx.unlock(); + WaitForSingleObject(event_, INFINITE); + ResetEvent(event_); // necessary? + mtx.lock(); +#else + pthread_cond_wait(&event_, &mtx.native_handle()); +#endif + } + + void wait_for(recursive_mutex &mtx, int milliseconds) { +#ifdef _WIN32 + //mtx.unlock(); + WaitForSingleObject(event_, milliseconds); + ResetEvent(event_); // necessary? + // mtx.lock(); +#else + timespec timeout; +#ifdef __APPLE__ + timeval tv; + gettimeofday(&tv, NULL); + timeout.tv_sec = tv.tv_sec; + timeout.tv_nsec = tv.tv_usec * 1000; +#else + clock_gettime(CLOCK_REALTIME, &timeout); +#endif + timeout.tv_sec += milliseconds / 1000; + timeout.tv_nsec += milliseconds * 1000000; + pthread_cond_timedwait(&event_, &mtx.native_handle(), &timeout); +#endif + } + +private: +#ifdef _WIN32 + HANDLE event_; +#else + pthread_cond_t event_; +#endif +}; diff --git a/file/file_util.cpp b/file/file_util.cpp index 25ad3eb741..fbaf027260 100644 --- a/file/file_util.cpp +++ b/file/file_util.cpp @@ -168,15 +168,12 @@ bool getFileInfo(const char *path, FileInfo *fileInfo) #ifdef _WIN32 fileInfo->size = 0; - FILE *f = fopen(path, "rb"); - if (f) { - fseek(f, 0, SEEK_END); - fileInfo->size = ftell(f); - fclose(f); + WIN32_FILE_ATTRIBUTE_DATA attrs; + if (GetFileAttributesExA(path, GetFileExInfoStandard, &attrs)) { + fileInfo->size = (uint64_t)attrs.nFileSizeLow | ((uint64_t)attrs.nFileSizeHigh << 32); } - DWORD attributes = GetFileAttributes(path); - fileInfo->isDirectory = (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - fileInfo->isWritable = (attributes & FILE_ATTRIBUTE_READONLY) == 0; + fileInfo->isDirectory = (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + fileInfo->isWritable = (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0; #else struct stat64 file_info; diff --git a/gfx/gl_lost_manager.cpp b/gfx/gl_lost_manager.cpp index 92d273706b..50a87449c2 100644 --- a/gfx/gl_lost_manager.cpp +++ b/gfx/gl_lost_manager.cpp @@ -1,12 +1,11 @@ #include +#include "base/basictypes.h" #include "base/logging.h" #include "gfx/gl_lost_manager.h" std::vector *holders; -GfxResourceHolder::~GfxResourceHolder() {} - static bool inLost; void register_gl_resource_holder(GfxResourceHolder *holder) { diff --git a/gfx/gl_lost_manager.h b/gfx/gl_lost_manager.h index 699d17075f..a8fb0249b3 100644 --- a/gfx/gl_lost_manager.h +++ b/gfx/gl_lost_manager.h @@ -5,7 +5,7 @@ class GfxResourceHolder { public: - virtual ~GfxResourceHolder(); + virtual ~GfxResourceHolder() {} virtual void GLLost() = 0; }; diff --git a/math/curves.cpp b/math/curves.cpp index 00c6303a50..be737cab29 100644 --- a/math/curves.cpp +++ b/math/curves.cpp @@ -33,6 +33,8 @@ float linearOut(int t, int fadeOutLength) { } float ease(float val) { + if (val > 1.0f) return 1.0f; + if (val < 0.0f) return 0.0f; return ((-cosf(val * PI)) + 1.0f) * 0.5; } diff --git a/native.vcxproj b/native.vcxproj index c54dbfeabf..b9aa090825 100644 --- a/native.vcxproj +++ b/native.vcxproj @@ -196,7 +196,6 @@ - @@ -242,6 +241,9 @@ + + + @@ -290,7 +292,6 @@ true - @@ -331,6 +332,8 @@ + + diff --git a/native.vcxproj.filters b/native.vcxproj.filters index 9baa377be3..3b902f1bd9 100644 --- a/native.vcxproj.filters +++ b/native.vcxproj.filters @@ -206,9 +206,6 @@ util - - base - ext @@ -239,6 +236,15 @@ ext + + thread + + + thread + + + thread + @@ -388,9 +394,6 @@ util - - base - ext @@ -423,6 +426,12 @@ ext + + thread + + + thread + @@ -482,5 +491,8 @@ {4515306f-4664-46bf-a89b-abfec5520a15} + + {caa41117-1d90-47cc-9fba-f7e670e315a3} + \ No newline at end of file diff --git a/thread/prioritizedworkqueue.cpp b/thread/prioritizedworkqueue.cpp new file mode 100644 index 0000000000..2866d3d8c8 --- /dev/null +++ b/thread/prioritizedworkqueue.cpp @@ -0,0 +1,82 @@ +#include + +#include "base/logging.h" +#include "thread/thread.h" +#include "thread/prioritizedworkqueue.h" + +#if defined(IOS) || (defined(__APPLE__) && !defined(__MAC_10_7)) +namespace std { + using tr1::bind; +} +#endif + +PrioritizedWorkQueue::~PrioritizedWorkQueue() { + if (!done_) { + ELOG("PrioritizedWorkQueue destroyed but not done!"); + } +} + +void PrioritizedWorkQueue::Add(PrioritizedWorkQueueItem *item) { + mutex_.lock(); + queue_.push_back(item); + mutex_.unlock(); + notEmpty_.notify_one(); +} + +void PrioritizedWorkQueue::Stop() { + mutex_.lock(); + done_ = true; + notEmpty_.notify_one(); + mutex_.unlock(); +} + +// The worker should simply call this in a loop. Will block when appropriate. +PrioritizedWorkQueueItem *PrioritizedWorkQueue::Pop() { + mutex_.lock(); + while (queue_.empty()) { + notEmpty_.wait(mutex_); + if (done_) + return 0; + } + + // Find the top priority item (lowest value). + float best_prio = std::numeric_limits::infinity(); + std::vector::iterator best = queue_.end(); + for (auto iter = queue_.begin(); iter != queue_.end(); ++iter) { + if ((*iter)->priority() < best_prio) { + best = iter; + best_prio = (*iter)->priority(); + } + } + PrioritizedWorkQueueItem *poppedItem = *best; + queue_.erase(best); + mutex_.unlock(); + return poppedItem; +} + +// TODO: This feels ugly. Revisit later. + +static std::thread *workThread; + +static void threadfunc(PrioritizedWorkQueue *wq) { + while (true) { + PrioritizedWorkQueueItem *item = wq->Pop(); + if (!item) { + if (wq->Done()) + break; + } else { + item->run(); + } + } +} + +void ProcessWorkQueueOnThreadWhile(PrioritizedWorkQueue *wq) { + workThread = new std::thread(std::bind(&threadfunc, wq)); +} + +void StopProcessingWorkQueue(PrioritizedWorkQueue *wq) { + wq->Stop(); + workThread->join(); + delete workThread; + workThread = 0; +} \ No newline at end of file diff --git a/thread/prioritizedworkqueue.h b/thread/prioritizedworkqueue.h new file mode 100644 index 0000000000..e3e815d7a3 --- /dev/null +++ b/thread/prioritizedworkqueue.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "base/basictypes.h" +#include "base/mutex.h" +#include "thread/threadutil.h" + +// Priorities can change dynamically. +// Try to make priority() fast, it will be called a lot. + +class PrioritizedWorkQueueItem { +public: + PrioritizedWorkQueueItem() {} + virtual ~PrioritizedWorkQueueItem() {} + virtual void run() = 0; + virtual float priority() = 0; // Low priority value = high priority! + +private: + DISALLOW_COPY_AND_ASSIGN(PrioritizedWorkQueueItem); +}; + +class PrioritizedWorkQueue { +public: + PrioritizedWorkQueue() : done_(false) {} + ~PrioritizedWorkQueue(); + // Takes ownership. + void Add(PrioritizedWorkQueueItem *item); + + // The worker should simply call this in a loop. Will block when appropriate. + PrioritizedWorkQueueItem *Pop(); + + bool Done() { return done_; } + void Stop(); + +private: + bool done_; + recursive_mutex mutex_; + condition_variable notEmpty_; + + std::vector queue_; + + DISALLOW_COPY_AND_ASSIGN(PrioritizedWorkQueue); +}; + + +// Starts up a thread that keeps trying to run this workqueue. +// TODO: This feels ugly. Revisit later. +void ProcessWorkQueueOnThreadWhile(PrioritizedWorkQueue *wq); +void StopProcessingWorkQueue(PrioritizedWorkQueue *wq); \ No newline at end of file diff --git a/thread/thread.h b/thread/thread.h new file mode 100644 index 0000000000..f32a96be92 --- /dev/null +++ b/thread/thread.h @@ -0,0 +1,310 @@ + +#ifndef STD_THREAD_H_ +#define STD_THREAD_H_ + +#define GCC_VER(x,y,z) ((x) * 10000 + (y) * 100 + (z)) +#define GCC_VERSION GCC_VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) + +#if GCC_VERSION >= GCC_VER(4,4,0) && __GXX_EXPERIMENTAL_CXX0X__ && !defined(ANDROID) && !defined(__SYMBIAN32__) +// GCC 4.4 provides +#ifndef _GLIBCXX_USE_SCHED_YIELD +#define _GLIBCXX_USE_SCHED_YIELD +#endif +#include +#else + +// partial std::thread implementation for win32/pthread + +#include + +#if (_MSC_VER >= 1600) || (GCC_VERSION >= GCC_VER(4,3,0) && __GXX_EXPERIMENTAL_CXX0X__) +#define USE_RVALUE_REFERENCES +#endif + +//#ifdef __APPLE__ +//#import +//#endif + +#if defined(_WIN32) +// WIN32 + +#define WIN32_LEAN_AND_MEAN +#include + +#if defined(_MSC_VER) && defined(_MT) +// When linking with LIBCMT (the multithreaded C library), Microsoft recommends +// using _beginthreadex instead of CreateThread. +#define USE_BEGINTHREADEX +#include +#endif + +#ifdef USE_BEGINTHREADEX +#define THREAD_ID unsigned +#define THREAD_RETURN unsigned __stdcall +#else +#define THREAD_ID DWORD +#define THREAD_RETURN DWORD WINAPI +#endif +#define THREAD_HANDLE HANDLE + +#else +// PTHREAD + +#include + +#ifndef _POSIX_THREADS +#error unsupported platform (no pthreads?) +#endif + +#include + +#define THREAD_ID pthread_t +#define THREAD_HANDLE pthread_t +#define THREAD_RETURN void* + +#endif + +namespace std +{ + +class thread +{ +public: + typedef THREAD_HANDLE native_handle_type; + + class id + { + friend class thread; + public: + id() : m_thread(0) {} + id(THREAD_ID _id) : m_thread(_id) {} + + bool operator==(const id& rhs) const + { + return m_thread == rhs.m_thread; + } + + bool operator!=(const id& rhs) const + { + return !(*this == rhs); + } + + bool operator<(const id& rhs) const + { + return m_thread < rhs.m_thread; + } + + private: + THREAD_ID m_thread; + }; + + // no variadic template support in msvc + //template + //thread(C&& func, A&&... args); + + template + thread(C func) + { + StartThread(new Func(func)); + } + + template + thread(C func, A arg) + { + StartThread(new FuncArg(func, arg)); + } + + thread() /*= default;*/ {} + +#ifdef USE_RVALUE_REFERENCES + thread(const thread&) /*= delete*/; + + thread(thread&& other) + { +#else + thread(const thread& t) + { + // ugly const_cast to get around lack of rvalue references + thread& other = const_cast(t); +#endif + swap(other); + } + +#ifdef USE_RVALUE_REFERENCES + thread& operator=(const thread&) /*= delete*/; + + thread& operator=(thread&& other) + { +#else + thread& operator=(const thread& t) + { + // ugly const_cast to get around lack of rvalue references + thread& other = const_cast(t); +#endif + if (joinable()) + detach(); + swap(other); + return *this; + } + + ~thread() + { + if (joinable()) + detach(); + } + + bool joinable() const + { + return m_id != id(); + } + + id get_id() const + { + return m_id; + } + + native_handle_type native_handle() + { +#ifdef _WIN32 + return m_handle; +#else + return m_id.m_thread; +#endif + } + + void join() + { +#ifdef _WIN32 + WaitForSingleObject(m_handle, INFINITE); + detach(); +#else + pthread_join(m_id.m_thread, NULL); + m_id = id(); +#endif + } + + void detach() + { +#ifdef _WIN32 + CloseHandle(m_handle); +#else + pthread_detach(m_id.m_thread); +#endif + m_id = id(); + } + + void swap(thread& other) + { + std::swap(m_id, other.m_id); +#ifdef _WIN32 + std::swap(m_handle, other.m_handle); +#endif + } + + static unsigned hardware_concurrency() + { +#ifdef _WIN32 + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + return static_cast(sysinfo.dwNumberOfProcessors); +#else + return 0; +#endif + } + +private: + id m_id; + +#ifdef _WIN32 + native_handle_type m_handle; +#endif + + template + void StartThread(F* param) + { +#ifdef USE_BEGINTHREADEX + m_handle = (HANDLE)_beginthreadex(NULL, 0, &RunAndDelete, param, 0, &m_id.m_thread); +#elif defined(_WIN32) + m_handle = CreateThread(NULL, 0, &RunAndDelete, param, 0, &m_id.m_thread); +#else + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, 1024 * 1024); + if (pthread_create(&m_id.m_thread, &attr, &RunAndDelete, param)) + m_id = id(); +#endif + } + + template + class Func + { + public: + Func(C _func) : func(_func) {} + + void Run() { func(); } + + private: + C const func; + }; + + template + class FuncArg + { + public: + FuncArg(C _func, A _arg) : func(_func), arg(_arg) {} + + void Run() { func(arg); } + + private: + C const func; + A arg; + }; + + template + static THREAD_RETURN RunAndDelete(void* param) + { +#ifdef __APPLE__ + // NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + static_cast(param)->Run(); + delete static_cast(param); +#ifdef __APPLE__ + // [pool release]; +#endif + return 0; + } +}; + +namespace this_thread +{ + +inline void yield() +{ +#ifdef _WIN32 + SwitchToThread(); +#else + sleep(0); +#endif +} + +inline thread::id get_id() +{ +#ifdef _WIN32 + return GetCurrentThreadId(); +#else + return pthread_self(); +#endif +} + +} // namespace this_thread + +} // namespace std + +#undef USE_RVALUE_REFERENCES +#undef USE_BEGINTHREADEX +#undef THREAD_ID +#undef THREAD_RETURN +#undef THREAD_HANDLE + +#endif +#endif diff --git a/base/threadutil.cpp b/thread/threadutil.cpp similarity index 95% rename from base/threadutil.cpp rename to thread/threadutil.cpp index d8447e039e..24ff853c1b 100644 --- a/base/threadutil.cpp +++ b/thread/threadutil.cpp @@ -1,7 +1,7 @@ #ifdef _WIN32 #include #endif -#include "base/threadutil.h" +#include "thread/threadutil.h" void setCurrentThreadName(const char* szThreadName) { diff --git a/base/threadutil.h b/thread/threadutil.h similarity index 64% rename from base/threadutil.h rename to thread/threadutil.h index 6b5d31990c..347562a26c 100644 --- a/base/threadutil.h +++ b/thread/threadutil.h @@ -3,7 +3,10 @@ #include #ifdef _WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN #include +#include #else #include @@ -36,3 +39,18 @@ public: } };*/ + + +#ifdef _WIN32 +#define THREAD_HANDLE HANDLE +#else +#define THREAD_HANDLE pthread_t +#endif + + +// TODO: replace this abomination with std::thread + +class thread { +public: + virtual void Run(); +}; \ No newline at end of file diff --git a/ui/screen.cpp b/ui/screen.cpp index edb31846e7..fd7661fab6 100644 --- a/ui/screen.cpp +++ b/ui/screen.cpp @@ -3,9 +3,6 @@ #include "ui/screen.h" #include "ui/ui.h" -Screen::Screen(bool isUiScreen) : screenManager_(0), isUiScreen_(isUiScreen) { } -Screen::~Screen() { } - ScreenManager::ScreenManager() { nextScreen_ = 0; uiContext_ = 0; diff --git a/ui/screen.h b/ui/screen.h index 883b381e5e..f77becca33 100644 --- a/ui/screen.h +++ b/ui/screen.h @@ -33,8 +33,9 @@ class UIContext; class Screen { public: - Screen(bool isUiScreen = false); - virtual ~Screen(); + Screen(bool isUiScreen = false) : screenManager_(0), isUiScreen_(isUiScreen) { } + virtual ~Screen() {} + virtual void update(InputState &input) = 0; virtual void render() {} virtual void deviceLost() {} diff --git a/ui/ui.cpp b/ui/ui.cpp index f894eb97e1..71e53c071e 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -234,7 +234,7 @@ int UIImageButton(int id, const LayoutManager &layout, float w, int image, int b return clicked; } -int UITextureButton(UIContext *ctx, int id, const LayoutManager &layout, float w, float h, Texture *texture, int button_align) // uses current UI atlas for fetching images. +int UITextureButton(UIContext *ctx, int id, const LayoutManager &layout, float w, float h, Texture *texture, int button_align, uint32_t color) // uses current UI atlas for fetching images. { float x, y; layout.GetPos(&w, &h, &x, &y); @@ -294,7 +294,7 @@ int UITextureButton(UIContext *ctx, int id, const LayoutManager &layout, float w Texture::Unbind(); } - ui_draw2d.DrawTexRect(x, y, x+w, y+h, 0, 0, 1, 1, 0xFFFFFFFF); + ui_draw2d.DrawTexRect(x, y, x+w, y+h, 0, 0, 1, 1, color); ui_draw2d.Flush(); ctx->RebindTexture(); diff --git a/ui/ui.h b/ui/ui.h index 237d57fed3..3bd7444661 100644 --- a/ui/ui.h +++ b/ui/ui.h @@ -20,7 +20,7 @@ #define GEN_ID_LOOP(i) (int)((IMGUI_SRC_ID) + (__LINE__) + (i) * 13612) #else #define GEN_ID (__LINE__) -#define GEN_ID_LOOP(i) ((__LINE__) + (i) * 13612) +#define GEN_ID_LOOP(i) ((__LINE__) + ((int)i) * 13612) #endif #include "gfx_es2/draw_buffer.h" @@ -244,7 +244,7 @@ void UIReset(); // Returns 1 if clicked int UIButton(int id, const LayoutManager &layout, float w, float h, const char *text, int button_align); int UIImageButton(int id, const LayoutManager &layout, float w, int image_id, int button_align); // uses current UI atlas for fetching images. -int UITextureButton(UIContext *ctx, int id, const LayoutManager &layout, float w, float h, Texture *texture, int button_align); // uses current UI atlas for fetching images. +int UITextureButton(UIContext *ctx, int id, const LayoutManager &layout, float w, float h, Texture *texture, int button_align, uint32_t color = 0xFFFFFFFF); // uses current UI atlas for fetching images. // Returns 1 if clicked, puts the value in *value (where it also gets the current state). int UICheckBox(int id, int x, int y, const char *text, int align, bool *value);