From 1ead202b24d24f48e818611ca44cb12976ca2b66 Mon Sep 17 00:00:00 2001 From: janwas Date: Wed, 31 May 2006 04:01:59 +0000 Subject: [PATCH] # add CppDoc comments; prepare for automated testing ia32: prepend CPUCap enum names and rdtsc with ia32 to avoid conflicts. move all self tests into separate headers as required for Cxxtest. adts: remove some dead code. add CppDoc comments to debug, lib (with heavy cleanup), tex, tex_codec, snd_mgr slight improvements to path tex: refactor; split out tex_decode and encode to allow self-test This was SVN commit r3911. --- source/graphics/Color.cpp | 2 +- source/lib/adts.cpp | 191 ---------- source/lib/adts.h | 154 --------- source/lib/allocators.cpp | 59 ---- source/lib/debug.cpp | 4 +- source/lib/debug.h | 441 ++++++++++++++--------- source/lib/lib.cpp | 461 +++++++++++-------------- source/lib/lib.h | 427 ++++++++++++++++------- source/lib/lockfree.cpp | 227 ------------ source/lib/lockfree.h | 2 +- source/lib/path_util.cpp | 3 + source/lib/path_util.h | 1 + source/lib/res/file/compression.h | 2 +- source/lib/res/file/file_cache.cpp | 75 ---- source/lib/res/file/path.cpp | 13 +- source/lib/res/file/zip.cpp | 37 -- source/lib/res/graphics/ogl_tex.cpp | 2 +- source/lib/res/graphics/tex.cpp | 420 +++++++++++----------- source/lib/res/graphics/tex.h | 368 +++++++++++++------- source/lib/res/graphics/tex_codec.cpp | 11 + source/lib/res/graphics/tex_codec.h | 224 +++++++++--- source/lib/res/graphics/tex_internal.h | 85 +++++ source/lib/res/sound/snd_mgr.h | 339 +++++++++++------- source/lib/self_test.h | 3 + source/lib/string_s.cpp | 197 ----------- source/lib/sysdep/ia32.cpp | 65 +--- source/lib/sysdep/ia32.h | 34 +- source/lib/sysdep/win/wdbg_sym.cpp | 243 ------------- source/lib/sysdep/win/wtime.cpp | 4 +- source/lib/timer.h | 6 +- source/maths/Matrix3D.cpp | 71 ---- source/ps/CStr.cpp | 30 -- source/ps/Parser.cpp | 112 ------ source/ps/XML/XMLWriter.cpp | 67 ---- 34 files changed, 1772 insertions(+), 2608 deletions(-) create mode 100644 source/lib/res/graphics/tex_internal.h diff --git a/source/graphics/Color.cpp b/source/graphics/Color.cpp index d87297641a..05fd62e66a 100644 --- a/source/graphics/Color.cpp +++ b/source/graphics/Color.cpp @@ -29,7 +29,7 @@ void ColorActivateFastImpl() { } #if CPU_IA32 - else if (ia32_cap(SSE)) + else if (ia32_cap(IA32_CAP_SSE)) { ConvertRGBColorTo4ub = sse_ConvertRGBColorTo4ub; } diff --git a/source/lib/adts.cpp b/source/lib/adts.cpp index a587408976..a6937b09fc 100644 --- a/source/lib/adts.cpp +++ b/source/lib/adts.cpp @@ -22,195 +22,4 @@ #include "precompiled.h" -#include - #include "adts.h" -#include "posix.h" -#include "lib/timer.h" - -//----------------------------------------------------------------------------- -// built-in self test -//----------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - -static void test_ringbuf() -{ - const size_t N = 49; // RingBuf capacity - const int S = 100; // number of test items - - // insert and remove immediately - { - RingBuf buf; - for(int i = 1; i < S; i++) - { - buf.push_back(i); - TEST(buf.front() == i); - buf.pop_front(); - } - TEST(buf.size() == 0 && buf.empty()); - } - - // fill buffer and overwrite old items - { - RingBuf buf; - for(int i = 1; i < S; i++) - buf.push_back(i); - TEST(buf.size() == N); - int first = buf.front(); - TEST(first == (int)(S-1 -N +1)); - for(size_t i = 0; i < N; i++) - { - TEST(buf.front() == first); - first++; - buf.pop_front(); - } - TEST(buf.size() == 0 && buf.empty()); - } - - // randomized insert/remove; must behave as does std::deque - { - srand(1); - RingBuf buf; - std::deque deq; - for(uint rep = 0; rep < 1000; rep++) - { - uint rnd_op = rand(0, 10); - // 70% - insert - if(rnd_op >= 3) - { - int item = rand(); - buf.push_back(item); - - deq.push_back(item); - int excess_items = (int)deq.size() - N; - if(excess_items > 0) - { - for(int i = 0; i < excess_items; i++) - { - deq.pop_front(); - } - } - } - // 30% - pop front (only if not empty) - else if(!deq.empty()) - { - buf.pop_front(); - deq.pop_front(); - } - } - TEST(buf.size() == deq.size()); - RingBuf::iterator begin = buf.begin(), end = buf.end(); - TEST(equal(begin, end, deq.begin())); - } -} - - - -// ensures all 3 variants of Landlord<> behave the same -static void test_cache_removal() -{ - Cache c1; - Cache c1r; - Cache c2; - Cache c2r; - Cache c3; - Cache c3r; - -#if defined(ENABLE_CACHE_POLICY_BENCHMARK) || 0 - // set max priority, to reduce interference while measuring. - int old_policy; static sched_param old_param; // (static => 0-init) - pthread_getschedparam(pthread_self(), &old_policy, &old_param); - static sched_param max_param; - max_param.sched_priority = sched_get_priority_max(SCHED_FIFO); - pthread_setschedparam(pthread_self(), SCHED_FIFO, &max_param); - -#define MEASURE(c, desc)\ -{\ -srand(1);\ -int cnt = 1;\ -TIMER_BEGIN(desc);\ -for(int i = 0; i < 30000; i++)\ -{\ - /* 70% add (random objects) */\ - bool add = rand(1,10) < 7;\ - if(add)\ - {\ - int key = cnt++;\ - int val = cnt++;\ - size_t size = (size_t)rand(1,100);\ - uint cost = (uint)rand(1,100);\ - c.add(key, val, size, cost);\ - }\ - else\ - {\ - size_t size;\ - int value;\ - c.remove_least_valuable(&value, &size);\ - }\ -}\ -TIMER_END(desc);\ -} - MEASURE(c1, "naive") - MEASURE(c1r, "naiverecip") - MEASURE(c2, "cached") - MEASURE(c2r, "cachedrecip") - MEASURE(c3, "lazy") - MEASURE(c3r, "lazyrecip") - - // restore previous policy and priority. - pthread_setschedparam(pthread_self(), old_policy, &old_param); -exit(1134); -#endif - - - srand(1); - int cnt = 1; - for(int i = 0; i < 1000; i++) - { - // 70% add (random objects) - bool add = rand(1,10) < 7; - if(add) - { - int key = cnt++; - int val = cnt++; - size_t size = (size_t)rand(1,100); - uint cost = (uint)rand(1,100); - c1.add(key, val, size, cost); - c2.add(key, val, size, cost); - c3.add(key, val, size, cost); - } - // 30% delete - make sure "least valuable" was same for all - else - { - size_t size1, size2, size3; - int value1, value2, value3; - bool removed1, removed2, removed3; - removed1 = c1.remove_least_valuable(&value1, &size1); - removed2 = c2.remove_least_valuable(&value2, &size2); - removed3 = c3.remove_least_valuable(&value3, &size3); - TEST(removed1 == removed2); - TEST(removed2 == removed3); - if (removed1) - { - TEST(size1 == size2); - TEST(value1 == value2); - TEST(size2 == size3); - TEST(value2 == value3); - } - } // else - } // for i -} - - -static void self_test() -{ - test_ringbuf(); - test_cache_removal(); -} - -SELF_TEST_REGISTER; - -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/lib/adts.h b/source/lib/adts.h index c888010c11..cde10a45e3 100644 --- a/source/lib/adts.h +++ b/source/lib/adts.h @@ -1307,158 +1307,4 @@ private: } }; - -// -// expansible hash table (linear probing) -// - - - - - - -// from VFS, not currently needed - -#if 0 -template class StringMap -{ -public: - - T* add(const char* fn, T& t) - { - const FnHash fn_hash = fnv_hash(fn); - - t.name = fn; - - std::pair item = std::make_pair(fn_hash, t); - std::pair res; - res = map.insert(item); - - if(!res.second) - { - debug_warn("add: already in container"); - return 0; - } - - // return address of user data (T) inserted into container. - return &((res.first)->second); - } - - T* find(const char* fn) - { - const FnHash fn_hash = fnv_hash(fn); - MapIt it = map.find(fn_hash); - // O(log(size)) - if(it == map.end()) - return 0; - return &it->second; - } - - size_t size() const - { - return map.size(); - } - - void clear() - { - map.clear(); - } - - -private: - typedef std::map Map; - typedef typename Map::iterator MapIt; - Map map; - - -public: - - class iterator - { - public: - iterator() - {} - iterator(typename StringMap::MapIt _it) - { it = _it; } - T& operator*() const - { return it->second; } - T* operator->() const - { return &**this; } - iterator& operator++() // pre - { ++it; return (*this); } - bool operator==(const iterator& rhs) const - { return it == rhs.it; } - bool operator!=(const iterator& rhs) const - { return !(*this == rhs); } - protected: - typename StringMap::MapIt it; - }; - - iterator begin() - { return iterator(map.begin()); } - - iterator end() - { return iterator(map.end()); } - -}; - - - -template class PriMap -{ -public: - - int add(Key key, uint pri, Data& data) - { - Item item = std::make_pair(pri, data); - MapEntry ent = std::make_pair(key, item); - std::pair ret; - ret = map.insert(ent); - // already in map - if(!ret.second) - { - MapIt it = ret.first; - Item item = it->second; - const uint old_pri = item.first; - Data& old_data = item.second; - - // new data is of higher priority; replace older data - if(old_pri <= pri) - { - old_data = data; - return 0; - } - // new data is of lower priority; don't add - else - return 1; - } - - return 0; - } - - Data* find(Key key) - { - MapIt it = map.find(key); - if(it == map.end()) - return 0; - - return &it->second.second; - } - - void clear() - { - map.clear(); - } - -private: - typedef std::pair Item; - typedef std::pair MapEntry; - typedef std::map Map; - typedef typename Map::iterator MapIt; - Map map; -}; -#endif // #if 0 - - - #endif // #ifndef ADTS_H__ diff --git a/source/lib/allocators.cpp b/source/lib/allocators.cpp index 70202141b8..2524feb324 100644 --- a/source/lib/allocators.cpp +++ b/source/lib/allocators.cpp @@ -862,62 +862,3 @@ void single_free(void* storage, volatile uintptr_t* in_use_flag, void* p) free(p); } } - - -//----------------------------------------------------------------------------- -// built-in self test -//----------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - -static void test_da() -{ - DynArray da; - - // basic test of functionality (not really meaningful) - TEST(da_alloc(&da, 1000) == 0); - TEST(da_set_size(&da, 1000) == 0); - TEST(da_set_prot(&da, PROT_NONE) == 0); - TEST(da_free(&da) == 0); - - // test wrapping existing mem blocks for use with da_read - u8 data[4] = { 0x12, 0x34, 0x56, 0x78 }; - TEST(da_wrap_fixed(&da, data, sizeof(data)) == 0); - u8 buf[4]; - TEST(da_read(&da, buf, 4) == 0); // success - TEST(read_le32(buf) == 0x78563412); // read correct value - TEST(da_read(&da, buf, 1) < 0); // no more data left - TEST(da_free(&da) == 0); -} - -static void test_expand() -{ -} - -static void test_matrix() -{ - // not much we can do here; allocate a matrix, write to it and - // make sure it can be freed. - // (note: can't check memory layout because "matrix" is int** - - // array of pointers. the matrix interface doesn't guarantee - // that data comes in row-major order after the row pointers) - int** m = (int**)matrix_alloc(3, 3, sizeof(int)); - m[0][0] = 1; - m[0][1] = 2; - m[1][0] = 3; - m[2][2] = 4; - matrix_free((void**)m); -} - -static void self_test() -{ - test_da(); - test_expand(); - test_matrix(); -} - -SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/lib/debug.cpp b/source/lib/debug.cpp index 43a61c62ae..d82c55816f 100644 --- a/source/lib/debug.cpp +++ b/source/lib/debug.cpp @@ -540,8 +540,6 @@ ErrorReaction debug_display_error(const wchar_t* description, } -// notify the user that an assertion failed; displays a stack trace with -// local variables. ErrorReaction debug_assert_failed(const char* expr, const char* file, int line, const char* func) { @@ -561,7 +559,7 @@ ErrorReaction debug_assert_failed(const char* expr, uint skip = 1; void* context = 0; wchar_t buf[400]; swprintf(buf, ARRAY_SIZE(buf), L"Assertion failed at %hs:%d (%hs): \"%hs\"", fn_only, line, func, expr); - return debug_display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip,context, fn_only,line); + return debug_display_error(buf, DE_ALLOW_SUPPRESS|DE_MANUAL_BREAK, skip, context, fn_only, line); } diff --git a/source/lib/debug.h b/source/lib/debug.h index 37700de969..e627aed3a6 100644 --- a/source/lib/debug.h +++ b/source/lib/debug.h @@ -30,9 +30,7 @@ # include "sysdep/unix/udbg.h" #endif -/* - -[KEEP IN SYNC WITH WIKI] +/** overview -------- @@ -70,31 +68,41 @@ motivation for this code is as follows: * mostly pertaining to Release mode - e.g. symbols cannot be resolved even if debug information is present and assert dialogs are useless. -*/ +**/ //----------------------------------------------------------------------------- // debug memory allocator //----------------------------------------------------------------------------- -// check heap integrity (independently of mmgr). -// errors are reported by the CRT or via debug_display_error. +/** + * check heap integrity (independently of mmgr). + * errors are reported by the CRT or via debug_display_error. + **/ extern void debug_heap_check(void); enum DebugHeapChecks { - // no automatic checks. (default) + /** + * no automatic checks. (default) + **/ DEBUG_HEAP_NONE = 0, - // basic automatic checks when deallocating. + /** + * basic automatic checks when deallocating. + **/ DEBUG_HEAP_NORMAL = 1, - // all automatic checks on every memory API call. this is really - // slow (x100), but reports errors closer to where they occurred. + /** + * all automatic checks on every memory API call. this is really + * slow (x100), but reports errors closer to where they occurred. + **/ DEBUG_HEAP_ALL = 2 }; -// call at any time; from then on, the specified checks will be performed. -// if not called, the default is DEBUG_HEAP_NONE, i.e. do nothing. +/** + * call at any time; from then on, the specified checks will be performed. + * if not called, the default is DEBUG_HEAP_NONE, i.e. do nothing. + **/ extern void debug_heap_enable(DebugHeapChecks what); @@ -102,25 +110,27 @@ extern void debug_heap_enable(DebugHeapChecks what); // debug_assert //----------------------------------------------------------------------------- -// rationale: we call this "debug_assert" instead of "assert" for the -// following reasons: -// - consistency (everything here is prefixed with debug_) and -// - to avoid inadvertent use of the much less helpful built-in CRT assert. -// if we were to override assert, it would be difficult to tell whether -// user source has included (possibly indirectly via other -// headers) and thereby stomped on our definition. - -// make sure the expression evaluates to non-zero. used to validate -// invariants in the program during development and thus gives a -// very helpful warning if something isn't going as expected. -// sprinkle these liberally throughout your code! -// -// recommended use is debug_assert(expression && "descriptive string") - -// the string can pass more information about the problem on to whomever -// is seeing the error. -// -// rationale: 0x55 and 0xAA are distinctive values and thus -// help debug the symbol engine. +/** + * make sure the expression evaluates to non-zero. used to validate + * invariants in the program during development and thus gives a + * very helpful warning if something isn't going as expected. + * sprinkle these liberally throughout your code! + * + * recommended use is debug_assert(expression && "descriptive string") - + * the string can pass more information about the problem on to whomever + * is seeing the error. + * + * rationale: we call this "debug_assert" instead of "assert" for the + * following reasons: + * - consistency (everything here is prefixed with debug_) and + * - to avoid inadvertent use of the much less helpful built-in CRT assert. + * if we were to override assert, it would be difficult to tell whether + * user source has included (possibly indirectly via other + * headers) and thereby stomped on our definition. + * + * implementation rationale: 0x55 and 0xAA are distinctive values and + * thus help debug the symbol engine. + **/ #define debug_assert(expr) \ STMT(\ static unsigned char suppress__ = 0x55;\ @@ -141,19 +151,22 @@ STMT(\ }\ ) - -// show a dialog to make sure unexpected states in the program are noticed. -// this is less error-prone than "debug_assert(0 && "text");" and avoids -// "conditional expression is constant" warnings. we'd really like to -// completely eliminate the problem; replacing 0 literals with extern -// volatile variables fools VC7 but isn't guaranteed to be free of overhead. -// we therefore just squelch the warning (unfortunately non-portable). +/** + * show a dialog to make sure unexpected states in the program are noticed. + * this is less error-prone than "debug_assert(0 && "text");" and avoids + * "conditional expression is constant" warnings. we'd really like to + * completely eliminate the problem; replacing 0 literals with extern + * volatile variables fools VC7 but isn't guaranteed to be free of overhead. + * we therefore just squelch the warning (unfortunately non-portable). + **/ #define debug_warn(str) debug_assert((str) && 0) -// if (LibError)err indicates an function failed, display the error dialog. -// used by CHECK_ERR et al., which wrap function calls and automatically -// warn user and return to caller. +/** + * if (LibError)err indicates an function failed, display the error dialog. + * used by CHECK_ERR et al., which wrap function calls and automatically + * warn user and return to caller. + **/ #define DEBUG_WARN_ERR(err)\ STMT(\ static unsigned char suppress__ = 0x55;\ @@ -175,12 +188,28 @@ STMT(\ ) -// called when an assertion has failed; notifies the user via debug_display_error. +/** + * called when a debug_assert fails; + * notifies the user via debug_display_error. + * + * @param assert_expr the expression that failed; typically passed as + * #expr in the assert macro. + * @param file, line source file name and line number of the spot that failed + * @param func name of the function containing it + * @return ErrorReaction (user's choice: continue running or stop?) + **/ extern ErrorReaction debug_assert_failed(const char* assert_expr, const char* file, int line, const char* func); -// called when a lib function wrapped in DEBUG_WARN_ERR failed; -// notifies the user via debug_display_error. +/** + * called when a DEBUG_WARN_ERR indicates an error occurred; + * notifies the user via debug_display_error. + * + * @param err LibError value indicating the error that occurred + * @param file, line source file name and line number of the spot that failed + * @param func name of the function containing it + * @return ErrorReaction (user's choice: continue running or stop?) + **/ extern ErrorReaction debug_warn_err(LibError err, const char* file, int line, const char* func); @@ -189,44 +218,65 @@ extern ErrorReaction debug_warn_err(LibError err, // output //----------------------------------------------------------------------------- -// write a formatted string to the debug channel, subject to filtering -// (see below). implemented via debug_puts - see performance note there. +/** + * write a formatted string to the debug channel, subject to filtering + * (see below). implemented via debug_puts - see performance note there. + * + * @param format string and varargs; see printf. + **/ extern void debug_printf(const char* fmt, ...); -// note: this merely converts to a MBS and calls debug_printf. + +/// note: this merely converts to a MBS and calls debug_printf. extern void debug_wprintf(const wchar_t* fmt, ...); -// translates and displays the given strings in a dialog. -// this is typically only used when debug_display_error has failed or -// is unavailable because that function is much more capable. -// implemented via sys_display_msgw; see documentation there. +/** + * translates and displays the given strings in a dialog. + * this is typically only used when debug_display_error has failed or + * is unavailable because that function is much more capable. + * implemented via sys_display_msgw; see documentation there. + **/ extern void debug_display_msgw(const wchar_t* caption, const wchar_t* msg); - +/// flags to customize debug_display_error behavior enum DisplayErrorFlags { - // allow the suppress button (requires calling via macro that - // maintains a 'suppress' bool; see debug_assert) + /** + * allow the suppress button (requires calling via macro that + * maintains a 'suppress' bool; see debug_assert) + **/ DE_ALLOW_SUPPRESS = 1, - // disallow the continue button. used e.g. if an exception is fatal. + /** + * disallow the continue button. used e.g. if an exception is fatal. + **/ DE_NO_CONTINUE = 2, - // do not trigger a breakpoint inside debug_display_error; caller - // will take care of this if ER_BREAK is returned. this is so that the - // debugger can jump directly into the offending function. + /** + * do not trigger a breakpoint inside debug_display_error; caller + * will take care of this if ER_BREAK is returned. this is so that the + * debugger can jump directly into the offending function. + **/ DE_MANUAL_BREAK = 4 }; -// display the error dialog. shows along with a stack trace. -// context and skip are as with debug_dump_stack. -// flags: see DisplayErrorFlags. file and line indicate where the error -// occurred and are typically passed as __FILE__, __LINE__. +/** + * display an error dialog with a message and stack trace. + * + * @param description text to show. + * @param flags: see DisplayErrorFlags. + * @param context, skip: see debug_dump_stack. + * @param file, line: location of the error (typically passed as + * __FILE__, __LINE__ from a macro) + * @return ErrorReaction (user's choice: continue running or stop?) + **/ extern ErrorReaction debug_display_error(const wchar_t* description, int flags, uint skip, void* context, const char* file, int line); -// convenience version, in case the advanced parameters aren't needed. -// macro instead of providing overload/default values for C compatibility. +/** + * convenience version, in case the advanced parameters aren't needed. + * macro instead of providing overload/default values for C compatibility. + **/ #define DISPLAY_ERROR(text) debug_display_error(text, 0, 0,0, __FILE__,__LINE__) @@ -234,41 +284,55 @@ extern ErrorReaction debug_display_error(const wchar_t* description, // filtering // -// debug output is very useful, but "too much of a good thing can kill you". -// we don't want to require different LOGn() macros that are enabled -// depending on "debug level", because changing that entails lengthy -// compiles and it's too coarse-grained. instead, we require all -// strings to start with "tag_string:" (exact case and no quotes; -// the alphanumeric-only identifies output type). -// they are then subject to filtering: only if the tag has been -// "added" via debug_filter_add is the appendant string displayed. -// -// this approach is easiest to implement and is fine because we control -// all logging code. LIMODS falls from consideration since it's not -// portable and too complex. -// -// notes: -// - filter changes only affect subsequent debug_*printf calls; -// output that didn't pass the filter is permanently discarded. -// - strings not starting with a tag are always displayed. -// - debug_filter_* can be called at any time and from the debugger. +/** + * debug output is very useful, but "too much of a good thing can kill you". + * we don't want to require different LOGn() macros that are enabled + * depending on "debug level", because changing that entails lengthy + * compiles and it's too coarse-grained. instead, we require all + * strings to start with "tag_string:" (exact case and no quotes; + * the alphanumeric-only identifies output type). + * they are then subject to filtering: only if the tag has been + * "added" via debug_filter_add is the appendant string displayed. + * + * this approach is easiest to implement and is fine because we control + * all logging code. LIMODS falls from consideration since it's not + * portable and too complex. + * + * notes: + * - filter changes only affect subsequent debug_*printf calls; + * output that didn't pass the filter is permanently discarded. + * - strings not starting with a tag are always displayed. + * - debug_filter_* can be called at any time and from the debugger. -// in future, allow output with the given tag to proceed. -// no effect if already added. + * in future, allow output with the given tag to proceed. + * no effect if already added. + **/ extern void debug_filter_add(const char* tag); -// in future, discard output with the given tag. -// no effect if not currently added. + +/** + * in future, discard output with the given tag. + * no effect if not currently added. + **/ extern void debug_filter_remove(const char* tag); -// clear all filter state; equivalent to debug_filter_remove for -// each tag that was debug_filter_add-ed. + +/** + * clear all filter state; equivalent to debug_filter_remove for + * each tag that was debug_filter_add-ed. + **/ extern void debug_filter_clear(); -// write to memory buffer (fast) -// used for "last activity" reporting in the crashlog. +/** + * write to memory buffer (fast) + * used for "last activity" reporting in the crashlog. + * + * @param format string and varags; see printf. + **/ extern void debug_wprintf_mem(const wchar_t* fmt, ...); -// write all logs and out to crashlog.txt (unicode format). +/** + * write all logs and out to crashlog.txt (unicode format). + **/ extern LibError debug_write_crashlog(const wchar_t* text); @@ -276,41 +340,52 @@ extern LibError debug_write_crashlog(const wchar_t* text); // breakpoints //----------------------------------------------------------------------------- -// trigger a breakpoint when reached/"called". -// defined as a macro by the platform-specific header above; this allows -// breaking directly into the target function, instead of one frame -// below it as with a conventional call-based implementation. -//#define debug_break() +/** + * trigger a breakpoint when reached/"called". + * defined as a macro by the platform-specific header above; this allows + * breaking directly into the target function, instead of one frame + * below it as with a conventional call-based implementation. + **/ +//#define debug_break() // not defined here; see above -// sometimes mmgr's 'fences' (making sure padding before and after the -// allocation remains intact) aren't enough to catch hard-to-find -// memory corruption bugs. another tool is to trigger a debug exception -// when the later to be corrupted variable is accessed; the problem should -// then become apparent. -// the VC++ IDE provides such 'breakpoints', but can only detect write access. -// additionally, it can't resolve symbols in Release mode (where this would -// be most useful), so we provide a breakpoint API. +/** + * sometimes mmgr's 'fences' (making sure padding before and after the + * allocation remains intact) aren't enough to catch hard-to-find + * memory corruption bugs. another tool is to trigger a debug exception + * when the later to be corrupted variable is accessed; the problem should + * then become apparent. + * the VC++ IDE provides such 'breakpoints', but can only detect write access. + * additionally, it can't resolve symbols in Release mode (where this would + * be most useful), so we provide a breakpoint API. -// (values chosen to match IA-32 bit defs, so compiler can optimize. -// this isn't required, it'll work regardless.) + * (values chosen to match IA-32 bit defs, so compiler can optimize. + * this isn't required; it'll work regardless.) + **/ enum DbgBreakType { - DBG_BREAK_CODE = 0, // execute - DBG_BREAK_DATA_WRITE = 1, // write - DBG_BREAK_DATA = 3 // read or write + DBG_BREAK_CODE = 0, /// execute + DBG_BREAK_DATA_WRITE = 1, /// write + DBG_BREAK_DATA = 3 /// read or write }; -// arrange for a debug exception to be raised when is accessed -// according to . -// for simplicity, the length (range of bytes to be checked) is derived -// from addr's alignment, and is typically 1 machine word. -// breakpoints are a limited resource (4 on IA-32); if none are -// available, we return ERR_LIMIT. +/** + * arrange for a debug exception to be raised when the + * indicated memory is accessed. + * + * @param addr memory address + * for simplicity, the length (range of bytes to be checked) is derived + * from addr's alignment, and is typically 1 machine word. + * @param type the type of access to watch for (see DbgBreakType) + * @return LibError; ERR_LIMIT if no more breakpoints are available + * (they are a limited resource - only 4 on IA-32). + **/ extern LibError debug_set_break(void* addr, DbgBreakType type); -// remove all breakpoints that were set by debug_set_break. -// important, since these are a limited resource. +/** + * remove all breakpoints that were set by debug_set_break. + * important, since these are a limited resource. + **/ extern LibError debug_remove_all_breaks(); @@ -318,28 +393,48 @@ extern LibError debug_remove_all_breaks(); // symbol access //----------------------------------------------------------------------------- -// maximum number of characters (including trailing \0) written to -// user's buffers by debug_resolve_symbol. +/** + * maximum number of characters (including trailing \0) written to + * user's buffers by debug_resolve_symbol. + **/ const size_t DBG_SYMBOL_LEN = 1000; const size_t DBG_FILE_LEN = 100; -// read and return symbol information for the given address. all of the -// output parameters are optional; we pass back as much information as is -// available and desired. return 0 iff any information was successfully -// retrieved and stored. -// sym_name and file must hold at least the number of chars above; -// file is the base name only, not path (see rationale in wdbg_sym). -// the PDB implementation is rather slow (~500us). +/** + * read and return symbol information for the given address. + * + * NOTE: the PDB implementation is rather slow (~500us). + * + * @param ptr_of_interest address of symbol (e.g. function, variable) + * @param sym_name optional out; size >= DBG_SYMBOL_LEN chars; + * receives symbol name returned via debug info. + * @param file optional out; size >= DBG_FILE_LEN chars; receives + * base name only (no path; see rationale in wdbg_sym) of + * source file containing the symbol. + * @param line optional out; receives source file line number of symbol. + * + * note: all of the output parameters are optional; we pass back as much + * information as is available and desired. + * @return LibError; ERR_OK iff any information was successfully + * retrieved and stored. + **/ extern LibError debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line); -// write a complete stack trace (including values of local variables) into -// the specified buffer. if is nonzero, it is assumed to be a -// platform-specific representation of execution state (e.g. Win32 CONTEXT) -// and tracing starts there; this is useful for exceptions. -// otherwise, tracing starts at the current stack position, and the given -// number of stack frames (i.e. functions) above the caller are skipped. -// this prevents functions like debug_assert_failed from -// cluttering up the trace. returns the buffer for convenience. +/** + * write a complete stack trace (including values of local variables) into + * the specified buffer. + * + * @param buf target buffer + * @param max_chars of buffer (should be several thousand) + * @param skip number of stack frames (i.e. functions on call stack) to skip. + * this prevents error-reporting functions like debug_assert_failed from + * cluttering up the trace. + * @param context platform-specific representation of execution state + * (e.g. Win32 CONTEXT). if not NULL, tracing starts there; this is useful + * for exceptions. otherwise, tracing starts from the current call stack. + * @return buf for convenience; writes an error string into it if + * something goes wrong. + **/ extern const wchar_t* debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip, void* context); @@ -347,50 +442,74 @@ extern const wchar_t* debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip // helper functions (used by implementation) //----------------------------------------------------------------------------- -// [system-dependent] write a string to the debug channel. -// this can be quite slow (~1 ms)! On Windows, it uses OutputDebugString -// (entails context switch), otherwise stdout+fflush (waits for IO). +/** + * [system-dependent] write a string to the debug channel. + * this can be quite slow (~1 ms)! On Windows, it uses OutputDebugString + * (entails context switch), otherwise stdout+fflush (waits for IO). + **/ extern void debug_puts(const char* text); -// abstraction of all STL iterators used by debug_stl. +/// abstraction of all STL iterators used by debug_stl. typedef const u8* (*DebugIterator)(void* internal, size_t el_size); -// return address of the Nth function on the call stack. -// if is nonzero, it is assumed to be a platform-specific -// representation of execution state (e.g. Win32 CONTEXT) and tracing -// starts there; this is useful for exceptions. -// otherwise, tracing starts at the current stack position, and the given -// number of stack frames (i.e. functions) above the caller are skipped. -// used by mmgr to determine what function requested each allocation; -// this is fast enough to allow that. +/** + * return address of the Nth function on the call stack. + * + * used by mmgr to determine what function requested each allocation; + * this is fast enough to allow that. + * + * @param skip number of stack frames (i.e. functions on call stack) to skip. + * @param context platform-specific representation of execution state + * (e.g. Win32 CONTEXT). if not NULL, tracing starts there; this is useful + * for exceptions. otherwise, tracing starts from the current call stack. + * @return address of Nth function + **/ extern void* debug_get_nth_caller(uint skip, void* context); -// return 1 if the pointer appears to be totally bogus, otherwise 0. -// this check is not authoritative (the pointer may be "valid" but incorrect) -// but can be used to filter out obviously wrong values in a portable manner. +/** + * check if a pointer appears to be totally invalid. + * + * this check is not authoritative (the pointer may be "valid" but incorrect) + * but can be used to filter out obviously wrong values in a portable manner. + * + * @param p pointer + * @return 1 if totally bogus, otherwise 0. + **/ extern int debug_is_pointer_bogus(const void* p); +/// does the given pointer appear to point to code? extern bool debug_is_code_ptr(void* p); + +/// does the given pointer appear to point to the stack? extern bool debug_is_stack_ptr(void* p); -// set the current thread's name; it will be returned by subsequent calls to -// debug_get_thread_name. -// -// the string pointed to by MUST remain valid throughout the -// entire program; best to pass a string literal. allocating a copy -// would be quite a bit more work due to cleanup issues. -// -// if supported on this platform, the debugger is notified of the new name; -// it will be displayed there instead of just the handle. +/** + * set the current thread's name; it will be returned by subsequent calls to + * debug_get_thread_name. + * + * if supported on this platform, the debugger is notified of the new name; + * it will be displayed there instead of just the handle. + * + * @param name identifier string for thread. MUST remain valid throughout + * the entire program; best to pass a string literal. allocating a copy + * would be quite a bit more work due to cleanup issues. + **/ extern void debug_set_thread_name(const char* name); -// return the pointer assigned by debug_set_thread_name or 0 if -// that hasn't been done yet for this thread. +/** + * return current thread's name. + * + * @return thread name, or NULL if one hasn't been assigned yet + * via debug_set_thread_name. + **/ extern const char* debug_get_thread_name(); -// call at exit to avoid leaks (not strictly necessary). +/** + * call at exit to avoid some leaks. + * not strictly necessary. + **/ extern void debug_shutdown(); #endif // #ifndef DEBUG_H_INCLUDED diff --git a/source/lib/lib.cpp b/source/lib/lib.cpp index 0a5e605fb6..04b58fd34b 100644 --- a/source/lib/lib.cpp +++ b/source/lib/lib.cpp @@ -33,119 +33,9 @@ #include "sysdep/sysdep.h" -#ifndef SELF_TEST_ENABLED -#define SELF_TEST_ENABLED 0 -#endif - - -// FNV1-A hash - good for strings. -// if len = 0 (default), treat buf as a C-string; -// otherwise, hash bytes of buf. -u32 fnv_hash(const void* buf, size_t len) -{ - u32 h = 0x811c9dc5u; - // give distinct values for different length 0 buffers. - // value taken from FNV; it has no special significance. - - const u8* p = (const u8*)buf; - - // expected case: string - if(!len) - { - while(*p) - { - h ^= *p++; - h *= 0x01000193u; - } - } - else - { - size_t bytes_left = len; - while(bytes_left != 0) - { - h ^= *p++; - h *= 0x01000193u; - - bytes_left--; - } - } - - return h; -} - - -// FNV1-A hash - good for strings. -// if len = 0 (default), treat buf as a C-string; -// otherwise, hash bytes of buf. -u64 fnv_hash64(const void* buf, size_t len) -{ - u64 h = 0xCBF29CE484222325ull; - // give distinct values for different length 0 buffers. - // value taken from FNV; it has no special significance. - - const u8* p = (const u8*)buf; - - // expected case: string - if(!len) - { - while(*p) - { - h ^= *p++; - h *= 0x100000001B3ull; - } - } - else - { - size_t bytes_left = len; - while(bytes_left != 0) - { - h ^= *p++; - h *= 0x100000001B3ull; - - bytes_left--; - } - } - - return h; -} - - -// special version for strings: first converts to lowercase -// (useful for comparing mixed-case filenames). -// note: still need , e.g. to support non-0-terminated strings -u32 fnv_lc_hash(const char* str, size_t len) -{ - u32 h = 0x811c9dc5u; - // give distinct values for different length 0 buffers. - // value taken from FNV; it has no special significance. - - // expected case: string - if(!len) - { - while(*str) - { - h ^= tolower(*str++); - h *= 0x01000193u; - } - } - else - { - size_t bytes_left = len; - while(bytes_left != 0) - { - h ^= tolower(*str++); - h *= 0x01000193u; - - bytes_left--; - } - } - - return h; -} - - - - +//----------------------------------------------------------------------------- +// bit bashing +//----------------------------------------------------------------------------- bool is_pow2(uint n) { @@ -196,7 +86,6 @@ int ilog2(uint n) return bit_index; } - // return log base 2, rounded up. uint log2(uint x) { @@ -211,8 +100,17 @@ uint log2(uint x) return l; } +int ilog2(const float x) +{ + const u32 i = *(u32*)&x; + u32 biased_exp = (i >> 23) & 0xff; + return (int)biased_exp - 127; +} -cassert(sizeof(int)*CHAR_BIT == 32); // otherwise change round_up_to_pow2 + +// round_up_to_pow2 implementation assumes 32-bit int. +// if 64, add "x |= (x >> 32);" +cassert(sizeof(int)*CHAR_BIT == 32); uint round_up_to_pow2(uint x) { @@ -227,13 +125,8 @@ uint round_up_to_pow2(uint x) } -int ilog2(const float x) -{ - const u32 i = *(u32*)&x; - u32 biased_exp = (i >> 23) & 0xff; - return (int)biased_exp - 127; -} - +//----------------------------------------------------------------------------- +// misc arithmetic // multiple must be a power of two. @@ -261,57 +154,71 @@ u16 addusw(u16 x, u16 y) return (u16)MIN(t+y, 0xffffu); } - u16 subusw(u16 x, u16 y) { long t = x; return (u16)(MAX(t-y, 0)); } -// zero-extend (truncated to 8) bytes of little-endian data to u64, -// starting at address

(need not be aligned). -u64 movzx_64le(const u8* p, size_t size) + +//----------------------------------------------------------------------------- +// rand + +// return random integer in [min, max). +// avoids several common pitfalls; see discussion at +// http://www.azillionmonkeys.com/qed/random.html + +// rand() is poorly implemented (e.g. in VC7) and only returns < 16 bits; +// double that amount by concatenating 2 random numbers. +// this is not to fix poor rand() randomness - the number returned will be +// folded down to a much smaller interval anyway. instead, a larger XRAND_MAX +// decreases the probability of having to repeat the loop. +#if RAND_MAX < 65536 +static const uint XRAND_MAX = (RAND_MAX+1)*(RAND_MAX+1) - 1; +static uint xrand() { - if(size > 8) - size = 8; - - u64 data = 0; - for(u64 i = 0; i < MIN(size,8); i++) - data |= ((u64)p[i]) << (i*8); - - return data; + return rand()*(RAND_MAX+1) + rand(); } - - -// sign-extend (truncated to 8) bytes of little-endian data to i64, -// starting at address

(need not be aligned). -i64 movsx_64le(const u8* p, size_t size) +// rand() is already ok; no need to do anything. +#else +static const uint XRAND_MAX = RAND_MAX; +static uint xrand() { - if(size > 8) - size = 8; + return rand(); +} +#endif - u64 data = movzx_64le(p, size); - - // no point in sign-extending if >= 8 bytes were requested - if(size < 8) +uint rand(uint min_inclusive, uint max_exclusive) +{ + const uint range = (max_exclusive-min_inclusive); + // huge interval or min >= max + if(range == 0 || range > XRAND_MAX) { - u64 sign_bit = 1; - sign_bit <<= (size*8)-1; - // be sure that we don't shift more than variable's bit width - - // number would be negative in the smaller type, - // so sign-extend, i.e. set all more significant bits. - if(data & sign_bit) - { - const u64 size_mask = (sign_bit+sign_bit)-1; - data |= ~size_mask; - } + WARN_ERR(ERR_INVALID_PARAM); + return 0; } - return (i64)data; + const uint inv_range = XRAND_MAX / range; + + // generate random number in [0, range) + // idea: avoid skewed distributions when doesn't evenly divide + // XRAND_MAX by simply discarding values in the "remainder". + // not expected to run often since XRAND_MAX is large. + uint x; + do + x = xrand(); + while(x >= range * inv_range); + x /= inv_range; + + x += min_inclusive; + debug_assert(x < max_exclusive); + return x; } +//----------------------------------------------------------------------------- +// type conversion + // these avoid a common mistake in using >> (ANSI requires shift count be // less than the bit width of the type). @@ -336,7 +243,6 @@ u16 u32_lo(u32 x) } - u64 u64_from_u32(u32 hi, u32 lo) { u64 x = (u64)hi; @@ -354,6 +260,45 @@ u32 u32_from_u16(u16 hi, u16 lo) } +// zero-extend (truncated to 8) bytes of little-endian data to u64, +// starting at address

(need not be aligned). +u64 movzx_64le(const u8* p, size_t size) +{ + size = MIN(size, 8); + + u64 data = 0; + for(u64 i = 0; i < size; i++) + data |= ((u64)p[i]) << (i*8); + + return data; +} + +// sign-extend (truncated to 8) bytes of little-endian data to i64, +// starting at address

(need not be aligned). +i64 movsx_64le(const u8* p, size_t size) +{ + size = MIN(size, 8); + + u64 data = movzx_64le(p, size); + + // no point in sign-extending if >= 8 bytes were requested + if(size < 8) + { + u64 sign_bit = 1; + sign_bit <<= (size*8)-1; + // be sure that we don't shift more than variable's bit width + + // number would be negative in the smaller type, + // so sign-extend, i.e. set all more significant bits. + if(data & sign_bit) + { + const u64 size_mask = (sign_bit+sign_bit)-1; + data |= ~size_mask; + } + } + + return (i64)data; +} // input in [0, 1); convert to u8 range @@ -370,7 +315,6 @@ u8 fp_to_u8(double in) return (u8)l; } - // input in [0, 1); convert to u16 range u16 fp_to_u16(double in) { @@ -386,18 +330,18 @@ u16 fp_to_u16(double in) } - - +//----------------------------------------------------------------------------- +// string processing // big endian! -void base32(const int len, const u8* in, u8* out) +void base32(const size_t len, const u8* in, u8* out) { - int bits = 0; - u32 pool = 0; + u32 pool = 0; // of bits from buffer + uint bits = 0; // # bits currently in buffer - static u8 tbl[33] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + static const u8 tbl[33] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - for(int i = 0; i < len; i++) + for(size_t i = 0; i < len; i++) { if(bits < 5) { @@ -407,20 +351,12 @@ void base32(const int len, const u8* in, u8* out) } bits -= 5; - int c = (pool >> bits) & 31; + uint c = (pool >> bits) & 31; *out++ = tbl[c]; } } - - - - -// case-insensitive check if string matches the pattern , -// which may contain '?' or '*' wildcards. if so, return 1, otherwise 0. -// idea from http://www.codeproject.com/string/wildcmp.asp . -// note: NULL wildcard pattern matches everything! int match_wildcard(const char* s, const char* w) { if(!w) @@ -520,110 +456,107 @@ int match_wildcardw(const wchar_t* s, const wchar_t* w) } - - -// return random integer in [min, max). -// avoids several common pitfalls; see discussion at -// http://www.azillionmonkeys.com/qed/random.html - -// rand() is poorly implemented (e.g. in VC7) and only returns < 16 bits; -// double that amount by concatenating 2 random numbers. -// this is not to fix poor rand() randomness - the number returned will be -// folded down to a much smaller interval anyway. instead, a larger XRAND_MAX -// decreases the probability of having to repeat the loop. -#if RAND_MAX < 65536 -static const uint XRAND_MAX = (RAND_MAX+1)*(RAND_MAX+1) - 1; -static uint xrand() +// FNV1-A hash - good for strings. +// if len = 0 (default), treat buf as a C-string; +// otherwise, hash bytes of buf. +u32 fnv_hash(const void* buf, size_t len) { - return rand()*(RAND_MAX+1) + rand(); -} -// rand() is already ok; no need to do anything. -#else -static const uint XRAND_MAX = RAND_MAX; -static uint xrand() -{ - return rand(); -} -#endif + u32 h = 0x811c9dc5u; + // give distinct values for different length 0 buffers. + // value taken from FNV; it has no special significance. -uint rand(uint min_inclusive, uint max_exclusive) -{ - const uint range = (max_exclusive-min_inclusive); - // huge interval or min >= max - if(range == 0 || range > XRAND_MAX) + const u8* p = (const u8*)buf; + + // expected case: string + if(!len) { - WARN_ERR(ERR_INVALID_PARAM); - return 0; + while(*p) + { + h ^= *p++; + h *= 0x01000193u; + } + } + else + { + size_t bytes_left = len; + while(bytes_left != 0) + { + h ^= *p++; + h *= 0x01000193u; + + bytes_left--; + } } - const uint inv_range = XRAND_MAX / range; - - // generate random number in [0, range) - // idea: avoid skewed distributions when doesn't evenly divide - // XRAND_MAX by simply discarding values in the "remainder". - // not expected to run often since XRAND_MAX is large. - uint x; - do - x = xrand(); - while(x >= range * inv_range); - x /= inv_range; - - x += min_inclusive; - debug_assert(x < max_exclusive); - return x; + return h; } -//----------------------------------------------------------------------------- -// built-in self test -//----------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - -static void test_log2() +// FNV1-A hash - good for strings. +// if len = 0 (default), treat buf as a C-string; +// otherwise, hash bytes of buf. +u64 fnv_hash64(const void* buf, size_t len) { - TEST(ilog2(0u) == -1); - TEST(ilog2(3u) == -1); - TEST(ilog2(0xffffffffu) == -1); - TEST(ilog2(1u) == 0); - TEST(ilog2(256u) == 8); - TEST(ilog2(0x80000000u) == 31); -} + u64 h = 0xCBF29CE484222325ull; + // give distinct values for different length 0 buffers. + // value taken from FNV; it has no special significance. -static void test_rand() -{ - // complain if huge interval or min > max - TEST(rand(1, 0) == 0); - TEST(rand(2, ~0u) == 0); + const u8* p = (const u8*)buf; - // returned number must be in [min, max) - for(int i = 0; i < 100; i++) + // expected case: string + if(!len) { - uint min = rand(), max = min+rand(); - uint x = rand(min, max); - TEST(min <= x && x < max); + while(*p) + { + h ^= *p++; + h *= 0x100000001B3ull; + } + } + else + { + size_t bytes_left = len; + while(bytes_left != 0) + { + h ^= *p++; + h *= 0x100000001B3ull; + + bytes_left--; + } } - // make sure both possible values are hit - uint ones = 0, twos = 0; - for(int i = 0; i < 100; i++) - { - uint x = rand(1, 3); - // paranoia: don't use array (x might not be 1 or 2 - checked below) - if(x == 1) ones++; if(x == 2) twos++; - } - TEST(ones+twos == 100); - TEST(ones > 10 && twos > 10); + return h; } -static void self_test() + +// special version for strings: first converts to lowercase +// (useful for comparing mixed-case filenames). +// note: still need , e.g. to support non-0-terminated strings +u32 fnv_lc_hash(const char* str, size_t len) { - test_log2(); - test_rand(); + u32 h = 0x811c9dc5u; + // give distinct values for different length 0 buffers. + // value taken from FNV; it has no special significance. + + // expected case: string + if(!len) + { + while(*str) + { + h ^= tolower(*str++); + h *= 0x01000193u; + } + } + else + { + size_t bytes_left = len; + while(bytes_left != 0) + { + h ^= tolower(*str++); + h *= 0x01000193u; + + bytes_left--; + } + } + + return h; } - -SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/lib/lib.h b/source/lib/lib.h index 194ef20260..8d431ebf77 100644 --- a/source/lib/lib.h +++ b/source/lib/lib.h @@ -20,9 +20,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ -/* - -[KEEP IN SYNC WITH WIKI] +/** low-level aka "lib" ------------------- @@ -53,7 +51,7 @@ scope - low-level helper functions, e.g. ADTs, endian conversion and timing - platform-dependent system/feature detection -*/ +**/ #ifndef LIB_H__ #define LIB_H__ @@ -66,31 +64,51 @@ scope #include "lib/types.h" #include "sysdep/sysdep.h" #include "sysdep/cpu.h" // CAS +//#include "sysdep/sysdep.h" // moved down; see below. #if defined(__cplusplus) -#define EXTERN_C extern "C" +# define EXTERN_C extern "C" #else -#define EXTERN_C extern +# define EXTERN_C extern #endif -// package code into a single statement. -// notes: -// - for(;;) { break; } and {} don't work because invocations of macros -// implemented with STMT often end with ";", thus breaking if() expressions. -// - we'd really like to eliminate "conditional expression is constant" -// warnings. replacing 0 literals with extern volatile variables fools -// VC7 but isn't guaranteed to be free of overhead. we will just -// squelch the warning (unfortunately non-portable). + +const size_t KiB = 1ul << 10; +const size_t MiB = 1ul << 20; +const size_t GiB = 1ul << 30; + + +//----------------------------------------------------------------------------- +// code-generating macros +//----------------------------------------------------------------------------- + +/** + * package code into a single statement. + * + * @param STMT_code__ code to be bundled. (must be interpretable as + * a macro argument, i.e. sequence of tokens). + * the argument name is chosen to avoid conflicts. + * + * notes: + * - for(;;) { break; } and {} don't work because invocations of macros + * implemented with STMT often end with ";", thus breaking if() expressions. + * - we'd really like to eliminate "conditional expression is constant" + * warnings. replacing 0 literals with extern volatile variables fools + * VC7 but isn't guaranteed to be free of overhead. we will just + * squelch the warning (unfortunately non-portable). + **/ #define STMT(STMT_code__) do { STMT_code__; } while(false) // must come after definition of STMT #include "lib/lib_errors.h" -// execute the code passed as a parameter only the first time this is -// reached. -// may be called at any time (in particular before main), but is not -// thread-safe. if that's important, use pthread_once() instead. +/** + * execute the code passed as a parameter only the first time this is + * reached. + * may be called at any time (in particular before main), but is not + * thread-safe. if that's important, use pthread_once() instead. + **/ #define ONCE(ONCE_code__)\ STMT(\ static bool ONCE_done__ = false;\ @@ -101,10 +119,12 @@ STMT(\ }\ ) -// execute the code passed as a parameter except the first time this is -// reached. -// may be called at any time (in particular before main), but is not -// thread-safe. +/** + * execute the code passed as a parameter except the first time this is + * reached. + * may be called at any time (in particular before main), but is not + * thread-safe. + **/ #define ONCE_NOT(ONCE_code__)\ STMT(\ static bool ONCE_done__ = false;\ @@ -115,8 +135,13 @@ STMT(\ ) -// useful because VC6 may return 0 on failure, instead of throwing. -// this wraps the exception handling, and creates a NULL pointer on failure. +/** + * C++ new wrapper: allocates an instance of the given type and stores a + * pointer to it. sets pointer to 0 on allocation failure. + * + * this simplifies application code when on VC6, which may or + * may not throw/return 0 on failure. + **/ #define SAFE_NEW(type, ptr)\ type* ptr;\ try\ @@ -128,13 +153,30 @@ STMT(\ ptr = 0;\ } +/** + * delete memory ensuing from new and set the pointer to zero + * (thus making double-frees safe / a no-op) + **/ #define SAFE_DELETE(p)\ STMT(\ delete (p); /* if p == 0, delete is a no-op */ \ (p) = 0;\ ) +/** + * delete memory ensuing from new[] and set the pointer to zero + * (thus making double-frees safe / a no-op) + **/ +#define SAFE_ARRAY_DELETE(p)\ +STMT(\ + delete[] (p); /* if p == 0, delete is a no-op */ \ + (p) = 0;\ +) +/** + * free memory ensuing from malloc and set the pointer to zero + * (thus making double-frees safe / a no-op) + **/ #define SAFE_FREE(p)\ STMT(\ free(p); /* if p == 0, free is a no-op */ \ @@ -142,33 +184,37 @@ STMT(\ ) +//----------------------------------------------------------------------------- +// source code annotation +//----------------------------------------------------------------------------- -#ifndef MIN -#define MIN(a, b) (((a) < (b))? (a) : (b)) -#endif - -#ifndef MAX -#define MAX(a, b) (((a) > (b))? (a) : (b)) -#endif - - -// 2 ways of avoiding "unreferenced formal parameter" warnings: -// .. inside the function body, e.g. void f(int x) { UNUSED2(x); } +/** + * mark a function local variable or parameter as unused and avoid + * the corresponding compiler warning. + * use inside the function body, e.g. void f(int x) { UNUSED2(x); } + **/ #define UNUSED2(param) (void)param; -// .. wrapped around the parameter name, e.g. void f(int UNUSED(x)) + +/** + * mark a function parameter as unused and avoid + * the corresponding compiler warning. + * wrap around the parameter name, e.g. void f(int UNUSED(x)) + **/ #define UNUSED(param) -// mark the copy constructor as inaccessible. this squelches -// "cannot be generated" warnings for classes with const members. -// -// intended to be used at end of class definition. -// must be followed by semicolon. +/** + * mark the copy constructor as inaccessible. this squelches + * "cannot be generated" warnings for classes with const members. + * + * intended to be used at end of class definition. + * must be followed by semicolon. + **/ #define NO_COPY_CTOR(class_name)\ private:\ class_name& operator=(const class_name&) -/* +/** "unreachable code" helpers unreachable lines of code are often the source or symptom of subtle bugs. @@ -200,7 +246,9 @@ our implementation of UNREACHABLE solves this dilemna as follows: this approach still allows for the possiblity of automated checking, but does not cause any compiler warnings. -*/ +**/ +#define UNREACHABLE // actually defined below.. this is for +# undef UNREACHABLE // CppDoc's benefit only. // 1) final build: optimize assuming this location cannot be reached. // may crash if that turns out to be untrue, but removes checking overhead. @@ -223,7 +271,7 @@ checking, but does not cause any compiler warnings. # endif #endif -/* +/** convenient specialization of UNREACHABLE for switch statements whose default can never be reached. example usage: int x; @@ -233,17 +281,11 @@ switch(x % 2) case 1: break; NODEFAULT; } -*/ +**/ #define NODEFAULT default: UNREACHABLE - -#define ARRAY_SIZE(name) (sizeof(name) / sizeof(name[0])) - - -// -// compile-time debug_assert, especially useful for testing sizeof(). -// no runtime overhead; may be used anywhere, including file scope. -// +//----------------------------------------------------------------------------- +// cassert // generate a symbol containing the line number of the macro invocation. // used to give a unique name (per file) to types made by cassert. @@ -254,37 +296,75 @@ switch(x % 2) #define MAKE_UID1__(l) MAKE_UID2__(l) #define UID__ MAKE_UID1__(__LINE__) -// more descriptive error message, but may cause a struct redefinition -// warning if used from the same line in different files. +/** + * compile-time debug_assert. causes a compile error if the expression + * evaluates to zero/false. + * + * no runtime overhead; may be used anywhere, including file scope. + * especially useful for testing sizeof types. + * + * this version has a more descriptive error message, but may cause a + * struct redefinition warning if used from the same line in different files. + * + * note: alternative method in C++: specialize a struct only for true; + * using it will raise 'incomplete type' errors if instantiated with false. + * + * @param expression that is expected to evaluate to non-zero at compile-time. + **/ #define cassert(expr) struct UID__ { int CASSERT_FAILURE: (expr); } -// less helpful error message, but redefinition doesn't trigger warnings. +/** + * compile-time debug_assert. causes a compile error if the expression + * evaluates to zero/false. + * + * no runtime overhead; may be used anywhere, including file scope. + * especially useful for testing sizeof types. + * + * this version has a less helpful error message, but redefinition doesn't + * trigger warnings. + * + * @param expression that is expected to evaluate to non-zero at compile-time. + **/ #define cassert2(expr) extern char CASSERT_FAILURE[1][(expr)] -// note: alternative method in C++: specialize a struct only for true; -// using it will raise 'incomplete type' errors if instantiated with false. - - - - -const size_t KiB = 1ul << 10; -const size_t MiB = 1ul << 20; -const size_t GiB = 1ul << 30; - - +//----------------------------------------------------------------------------- +// bit bashing +//----------------------------------------------------------------------------- +/** + * value of bit number . + * + * @param n bit index (0..CHAR_BIT*sizeof(int)-1) + **/ #define BIT(n) (1ul << (n)) // these are declared in the header and inlined to aid compiler optimizations // (they can easily end up being time-critical). +// note: GCC can't inline extern functions, while VC's "Whole Program +// Optimization" can. +/** + * a mask that includes the lowest N bits + * + * @param num_bits number of bits in mask + **/ inline uint bit_mask(uint num_bits) { return (1u << num_bits)-1; } +/** + * extract the value of bits hi_idx:lo_idx within num + * + * example: bits(0x69, 2, 5) == 0x0A + * + * @param num number whose bits are to be extracted + * @param lo_idx bit index of lowest bit to include + * @param hi_idx bit index of highest bit to include + * @return value of extracted bits. + **/ inline uint bits(uint num, uint lo_idx, uint hi_idx) { const uint count = (hi_idx - lo_idx)+1; // # bits to return @@ -293,93 +373,182 @@ inline uint bits(uint num, uint lo_idx, uint hi_idx) return result; } - -// FNV1-A hash - good for strings. -// if len = 0 (default), treat buf as a C-string; -// otherwise, hash bytes of buf. -extern u32 fnv_hash(const void* buf, size_t len = 0); -extern u64 fnv_hash64(const void* buf, size_t len = 0); - -// special version for strings: first converts to lowercase -// (useful for comparing mixed-case filenames) -extern u32 fnv_lc_hash(const char* str, size_t len = 0); - -// hash (currently FNV) of a filename -typedef u32 FnHash; - - -extern u16 addusw(u16 x, u16 y); -extern u16 subusw(u16 x, u16 y); - -// zero-extend (truncated to 8) bytes of little-endian data to u64, -// starting at address

(need not be aligned). -extern u64 movzx_64le(const u8* p, size_t size); - -// sign-extend (truncated to 8) bytes of little-endian data to i64, -// starting at address

(need not be aligned). -extern i64 movsx_64le(const u8* p, size_t size); - - +/// is the given number a power of two? extern bool is_pow2(uint n); -// return -1 if not an integral power of 2, -// otherwise the base2 logarithm +/** + * @return -1 if not an integral power of 2, + * otherwise the base2 logarithm. + **/ extern int ilog2(uint n); -// return log base 2, rounded up. +/** + * @return log base 2, rounded up. + **/ extern uint log2(uint x); +/** + * another implementation; uses the FPU normalization hardware. + * + * @return log base 2, rounded up. + **/ +extern int ilog2(const float x); + +/** + * round up to nearest power of two; no change if already POT. + **/ extern uint round_up_to_pow2(uint x); -// multiple must be a power of two. +//----------------------------------------------------------------------------- +// misc arithmetic + +/// canonical minimum macro +#ifndef MIN +#define MIN(a, b) (((a) < (b))? (a) : (b)) +#endif + +/// canonical maximum macro +#ifndef MAX +#define MAX(a, b) (((a) > (b))? (a) : (b)) +#endif + +/// number of array elements +#define ARRAY_SIZE(name) (sizeof(name) / sizeof(name[0])) + +/** + * round number up/down to the next given multiple. + * + * @param multiple: must be a power of two. + **/ extern uintptr_t round_up (uintptr_t n, uintptr_t multiple); extern uintptr_t round_down(uintptr_t n, uintptr_t multiple); -// these avoid a common mistake in using >> (ANSI requires shift count be -// less than the bit width of the type). -extern u32 u64_hi(u64 x); -extern u32 u64_lo(u64 x); -extern u16 u32_hi(u32 x); -extern u16 u32_lo(u32 x); - - -extern u64 u64_from_u32(u32 hi, u32 lo); -extern u32 u32_from_u16(u16 hi, u16 lo); - - - +/// 16-bit saturating (does not overflow) addition. +extern u16 addusw(u16 x, u16 y); +/// 16-bit saturating (does not underflow) subtraction. +extern u16 subusw(u16 x, u16 y); +/** + * are the given floats nearly "equal"? + * + * @return whether the numbers are within "epsilon" of each other. + * + * notes: + * - the epsilon magic number varies with the magnitude of the inputs. + * we use a sane default, but don't use this routine for very + * large/small comparands. + * - floating-point numbers don't magically lose precision. addition, + * subtraction and multiplication results are precise up to the mantissa's + * least-significant bit. only division, sqrt, sin/cos and other + * trancendental operations introduce error. + **/ inline bool feq(float f1, float f2) { - // the requisite value will change with the magnitude of f1 and f2! - // this is a sane default, but don't use this routine for very - // large/small comparands. const float epsilon = 0.00001f; return fabsf(f1 - f2) < epsilon; } +/** +* return random integer in [min, max). +* avoids several common pitfalls; see discussion at +* http://www.azillionmonkeys.com/qed/random.html +**/ +extern uint rand(uint min_inclusive, uint max_exclusive); + + +//----------------------------------------------------------------------------- +// type conversion + + +// note: these avoid a common mistake in using >> (ANSI requires +// shift count be less than the bit width of the type). + +extern u32 u64_hi(u64 x); /// return upper 32-bits +extern u32 u64_lo(u64 x); /// return lower 32-bits +extern u16 u32_hi(u32 x); /// return upper 16-bits +extern u16 u32_lo(u32 x); /// return lower 16-bits + +extern u64 u64_from_u32(u32 hi, u32 lo); /// assemble u64 from u32 +extern u32 u32_from_u16(u16 hi, u16 lo); /// assemble u32 from u16 + +/** + * zero-extend (truncated to 8) bytes of little-endian data to u64, + * starting at address

(need not be aligned). + **/ +extern u64 movzx_64le(const u8* p, size_t size); + +/** + * sign-extend (truncated to 8) bytes of little-endian data to i64, + * starting at address

(need not be aligned). + **/ +extern i64 movsx_64le(const u8* p, size_t size); + +/// convert double to u8; verifies number is in range. +extern u8 fp_to_u8 (double in); +/// convert double to u16; verifies number is in range. extern u16 fp_to_u16(double in); -// big endian! -extern void base32(const int len, const u8* in, u8* out); +//----------------------------------------------------------------------------- +// string processing - -// case-insensitive check if string matches the pattern , -// which may contain '?' or '*' wildcards. if so, return 1, otherwise 0. -// note: NULL wildcard pattern matches everything! -extern int match_wildcard(const char* s, const char* w); -extern int match_wildcardw(const wchar_t* s, const wchar_t* w); - -// this is strcpy, but indicates that the programmer checked usage and -// promises it is safe. +/** + * this is strcpy, but indicates that the programmer checked usage and + * promises it is safe. + **/ #define SAFE_STRCPY strcpy -// return random integer in [min, max). -// avoids several common pitfalls; see discussion at -// http://www.azillionmonkeys.com/qed/random.html -extern uint rand(uint min_inclusive, uint max_exclusive); + +/** + * generate the base32 textual representation of a buffer. + * + * @param len size [bytes] of input + * @param big-endian input data (assumed to be integral number of bytes) + * @param output string; zero-terminated. must be big enough + * (i.e. at least ceil(len*CHAR_BIT/5) + 1 chars) + **/ +extern void base32(const size_t len, const u8* in, u8* out); + + +/** + * partial regex implementation: see if string matches pattern. + * + * @param s input string + * @param w pseudo-regex to match against. case-insensitive; + * may contain '?' and/or '*' wildcards. if NULL, matches everything. + * + * @return 1 if they match, otherwise 0. + * + * algorithmfrom http://www.codeproject.com/string/wildcmp.asp. + **/ +extern int match_wildcard(const char* s, const char* w); +/// unicode version of match_wildcard. +extern int match_wildcardw(const wchar_t* s, const wchar_t* w); + + +/** + * calculate FNV1-A hash. + * + * @param buf input buffer. + * @param len if 0 (default), treat buf as a C-string; otherwise, + * indicates how many bytes of buffer to hash. + * @return hash result. note: results are distinct for buffers containing + * differing amounts of zero bytes because the hash value is seeded. + * + * rationale: this algorithm was chosen because it delivers 'good' results + * for string data and is relatively simple. other good alternatives exist; + * see Ozan Yigit's hash roundup. + **/ +extern u32 fnv_hash(const void* buf, size_t len = 0); +/// 64-bit version of fnv_hash. +extern u64 fnv_hash64(const void* buf, size_t len = 0); + +/** + * special version of fnv_hash for strings: first converts to lowercase + * (useful for comparing mixed-case filenames) + **/ +extern u32 fnv_lc_hash(const char* str, size_t len = 0); #endif // #ifndef LIB_H__ diff --git a/source/lib/lockfree.cpp b/source/lib/lockfree.cpp index 84663229c8..c2f7c25192 100644 --- a/source/lib/lockfree.cpp +++ b/source/lib/lockfree.cpp @@ -31,10 +31,6 @@ #include "lockfree.h" #include "timer.h" -// known to fail on P4 due to mem reordering and lack of membars. -#undef SELF_TEST_ENABLED -#define SELF_TEST_ENABLED 0 - /* liberties taken: - R(H) will remain constant @@ -743,226 +739,3 @@ LibError lfh_erase(LFHash* hash, uintptr_t key) { return lfl_erase(chain(hash,key), key); } - - -////////////////////////////////////////////////////////////////////////////// -// -// built-in self test -// -////////////////////////////////////////////////////////////////////////////// - -#if SELF_TEST_ENABLED -namespace test { - -#define TEST_CALL(expr) TEST(expr == 0) - -// make sure the data structures work at all; doesn't test thread-safety. -static void basic_single_threaded_test() -{ - void* user_data; - - const uint ENTRIES = 50; - // should be more than max # retired nodes to test release..() code - uintptr_t key = 0x1000; - uint sig = 10; - - LFList list; - TEST_CALL(lfl_init(&list)); - - LFHash hash; - TEST_CALL(lfh_init(&hash, 8)); - - // add some entries; store "signatures" (ascending int values) - for(uint i = 0; i < ENTRIES; i++) - { - int was_inserted; - - user_data = lfl_insert(&list, key+i, sizeof(int), &was_inserted); - TEST(user_data != 0 && was_inserted); - *(uint*)user_data = sig+i; - - user_data = lfh_insert(&hash, key+i, sizeof(int), &was_inserted); - TEST(user_data != 0 && was_inserted); - *(uint*)user_data = sig+i; - } - - // make sure all "signatures" are present in list - for(uint i = 0; i < ENTRIES; i++) - { - user_data = lfl_find(&list, key+i); - TEST(user_data != 0); - TEST(*(uint*)user_data == sig+i); - - user_data = lfh_find(&hash, key+i); - TEST(user_data != 0); - TEST(*(uint*)user_data == sig+i); - - } - - lfl_free(&list); - lfh_free(&hash); -} - - -// -// multithreaded torture test -// - -// poor man's synchronization "barrier" -static bool is_complete; -static intptr_t num_active_threads; - -static LFList list; -static LFHash hash; - -typedef std::set KeySet; -typedef KeySet::const_iterator KeySetIt; -static KeySet keys; -static pthread_mutex_t mutex; // protects - - -static void* thread_func(void* arg) -{ - debug_set_thread_name("LF_test"); - - const uintptr_t thread_number = (uintptr_t)arg; - - atomic_add(&num_active_threads, 1); - - // chosen randomly every iteration (int_value % 4) - enum TestAction - { - TA_FIND = 0, - TA_INSERT = 1, - TA_ERASE = 2, - TA_SLEEP = 3 - }; - static const char* const action_strings[] = - { - "find", "insert", "erase", "sleep" - }; - - while(!is_complete) - { - void* user_data; - - const int action = rand(0, 4); - const uintptr_t key = rand(0, 100); - const int sleep_duration_ms = rand(0, 100); - debug_printf("thread %d: %s\n", thread_number, action_strings[action]); - - // - pthread_mutex_lock(&mutex); - const bool was_in_set = keys.find(key) != keys.end(); - if(action == TA_INSERT) - keys.insert(key); - else if(action == TA_ERASE) - keys.erase(key); - pthread_mutex_unlock(&mutex); - - switch(action) - { - case TA_FIND: - { - user_data = lfl_find(&list, key); - TEST(was_in_set == (user_data != 0)); - if(user_data) - TEST(*(uintptr_t*)user_data == ~key); - - user_data = lfh_find(&hash, key); - // typical failure site if lockfree data structure has bugs. - TEST(was_in_set == (user_data != 0)); - if(user_data) - TEST(*(uintptr_t*)user_data == ~key); - } - break; - - case TA_INSERT: - { - int was_inserted; - - user_data = lfl_insert(&list, key, sizeof(uintptr_t), &was_inserted); - TEST(user_data != 0); // only triggers if out of memory - *(uintptr_t*)user_data = ~key; // checked above - TEST(was_in_set == !was_inserted); - - user_data = lfh_insert(&hash, key, sizeof(uintptr_t), &was_inserted); - TEST(user_data != 0); // only triggers if out of memory - *(uintptr_t*)user_data = ~key; // checked above - TEST(was_in_set == !was_inserted); - } - break; - - case TA_ERASE: - { - int err; - - err = lfl_erase(&list, key); - TEST(was_in_set == (err == ERR_OK)); - - err = lfh_erase(&hash, key); - TEST(was_in_set == (err == ERR_OK)); - } - break; - - case TA_SLEEP: - usleep(sleep_duration_ms*1000); - break; - - default: - DISPLAY_ERROR(L"invalid TA_* action"); - break; - } // switch - } // while !is_complete - - atomic_add(&num_active_threads, -1); - TEST(num_active_threads >= 0); - - return 0; -} - - -static void multithreaded_torture_test() -{ - // this test is randomized; we need deterministic results. - srand(1); - - static const double TEST_LENGTH = 30.; // [seconds] - const double end_time = get_time() + TEST_LENGTH; - is_complete = false; - - WARN_ERR(lfl_init(&list)); - WARN_ERR(lfh_init(&hash, 128)); - WARN_ERR(pthread_mutex_init(&mutex, 0)); - - // spin off test threads (many, to force preemption) - const uint NUM_THREADS = 16; - for(uintptr_t i = 0; i < NUM_THREADS; i++) - pthread_create(0, 0, thread_func, (void*)i); - - // wait until time interval elapsed (if we get that far, all is well). - while(get_time() < end_time) - usleep(10*1000); - - // signal and wait for all threads to complete (poor man's barrier - - // those aren't currently implemented in wpthread). - is_complete = true; - while(num_active_threads > 0) - usleep(5*1000); - - lfl_free(&list); - lfh_free(&hash); - WARN_ERR(pthread_mutex_destroy(&mutex)); -} - - -static void self_test() -{ - basic_single_threaded_test(); - multithreaded_torture_test(); -} - -SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/lib/lockfree.h b/source/lib/lockfree.h index 01aec138d9..9735b99fd6 100644 --- a/source/lib/lockfree.h +++ b/source/lib/lockfree.h @@ -80,7 +80,7 @@ useful "payload" in the data structures is allocated when inserting each item: additional_bytes are appended. rationale: see struct Node definition. since lock-free algorithms are subtle and easy to get wrong, an extensive -self-test is included; #define SELF_TEST_ENABLED 1 to activate. +self-test is included. terminology diff --git a/source/lib/path_util.cpp b/source/lib/path_util.cpp index 3b80a79dc9..ca2c6448fe 100644 --- a/source/lib/path_util.cpp +++ b/source/lib/path_util.cpp @@ -38,6 +38,7 @@ static inline bool is_dir_sep(char c) // is s2 a subpath of s1, or vice versa? +// (equal counts as subpath) bool path_is_subpath(const char* s1, const char* s2) { // make sure s1 is the shorter string @@ -233,6 +234,8 @@ const char* path_name_only(const char* path) return path; } +// TODO: take max of portableslash, nonportableslash + const char* name = slash+1; return name; } diff --git a/source/lib/path_util.h b/source/lib/path_util.h index f26eef7251..8f1cb696aa 100644 --- a/source/lib/path_util.h +++ b/source/lib/path_util.h @@ -46,6 +46,7 @@ extern LibError path_component_validate(const char* name); // is s2 a subpath of s1, or vice versa? +// (equal counts as subpath) extern bool path_is_subpath(const char* s1, const char* s2); // if path is invalid, return a descriptive error code, otherwise ERR_OK. diff --git a/source/lib/res/file/compression.h b/source/lib/res/file/compression.h index 845ad7a18d..ddf692b4c4 100644 --- a/source/lib/res/file/compression.h +++ b/source/lib/res/file/compression.h @@ -55,7 +55,7 @@ extern void comp_set_output(uintptr_t ctx, void* out, size_t out_size); // reliably estimate how much output space is needed. // raises a warning for decompression contexts because this operation // does not make sense there: -// - decompression ratio is quite large - ballpark 1000x; +// - worst-case decompression ratio is quite large - ballpark 1000x; // - exact uncompressed size is known to caller (via archive file header). // note: buffer is held until comp_free; it can be reused after a // comp_reset. this reduces malloc/free calls. diff --git a/source/lib/res/file/file_cache.cpp b/source/lib/res/file/file_cache.cpp index 9ec6bc6fd8..1710479f04 100644 --- a/source/lib/res/file/file_cache.cpp +++ b/source/lib/res/file/file_cache.cpp @@ -1286,78 +1286,3 @@ void file_cache_shutdown() cache_allocator.shutdown(); block_mgr.shutdown(); } - - -//----------------------------------------------------------------------------- -// built-in self test -//----------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - -static void test_cache_allocator() -{ - // allocated address -> its size - typedef std::map AllocMap; - AllocMap allocations; - - // put allocator through its paces by allocating several times - // its capacity (this ensures memory is reused) - srand(1); - size_t total_size_used = 0; - while(total_size_used < 4*MAX_CACHE_SIZE) - { - size_t size = rand(1, MAX_CACHE_SIZE/4); - total_size_used += size; - void* p; - // until successful alloc: - for(;;) - { - p = cache_allocator.alloc(size); - if(p) - break; - // out of room - remove a previous allocation - // .. choose one at random - size_t chosen_idx = (size_t)rand(0, (uint)allocations.size()); - AllocMap::iterator it = allocations.begin(); - for(; chosen_idx != 0; chosen_idx--) - ++it; - cache_allocator.dealloc((u8*)it->first, it->second); - allocations.erase(it); - } - - // must not already have been allocated - TEST(allocations.find(p) == allocations.end()); - allocations[p] = size; - } - - // reset to virginal state - cache_allocator.reset(); -} - -static void test_file_cache() -{ - // we need a unique address for file_cache_add, but don't want to - // actually put it in the atom_fn storage (permanently clutters it). - // just increment this pointer (evil but works since it's not used). -// const char* atom_fn = (const char*)1; - // give to file_cache -// file_cache_add((FileIOBuf)p, size, atom_fn++); - - file_cache_reset(); - TEST(file_cache.empty()); - - // note: even though everything has now been freed, - // the freelists may be a bit scattered already. -} - -static void self_test() -{ - test_cache_allocator(); - test_file_cache(); -} - -SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/lib/res/file/path.cpp b/source/lib/res/file/path.cpp index b0cb4a86e1..927a32b5ac 100644 --- a/source/lib/res/file/path.cpp +++ b/source/lib/res/file/path.cpp @@ -199,6 +199,11 @@ LibError file_set_root_dir(const char* argv0, const char* rel_path) // arena, which is also more memory-efficient than the heap (no headers). static Pool atom_pool; +bool path_is_atom_fn(const char* fn) +{ + return pool_contains(&atom_pool, (void*)fn); +} + // allocate a copy of P_fn in our string pool. strings are equal iff // their addresses are equal, thus allowing fast comparison. // @@ -208,7 +213,7 @@ static Pool atom_pool; const char* file_make_unique_fn_copy(const char* P_fn) { // early out: if already an atom, return immediately. - if(pool_contains(&atom_pool, (void*)P_fn)) + if(path_is_atom_fn(P_fn)) return P_fn; const size_t fn_len = strlen(P_fn); @@ -241,12 +246,6 @@ const char* file_make_unique_fn_copy(const char* P_fn) } -bool path_is_atom_fn(const char* fn) -{ - return pool_contains(&atom_pool, (void*)fn); -} - - void path_init() { pool_create(&atom_pool, 8*MiB, POOL_VARIABLE_ALLOCS); diff --git a/source/lib/res/file/zip.cpp b/source/lib/res/file/zip.cpp index 53ee87431b..2f01ee6ffa 100644 --- a/source/lib/res/file/zip.cpp +++ b/source/lib/res/file/zip.cpp @@ -671,40 +671,3 @@ LibError zip_archive_finish(ZipArchive* za) za_mgr.release(za); return ERR_OK; } - - -//----------------------------------------------------------------------------- -// built-in self test -//----------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - -static void test_fat_timedate_conversion() -{ - // note: FAT time stores second/2, which means converting may - // end up off by 1 second. - - time_t t, converted_t; - long dt; - - t = time(0); - converted_t = time_t_from_FAT(FAT_from_time_t(t)); - dt = converted_t-t; // disambiguate abs() parameter - TEST(abs(dt) < 2); - - t++; - converted_t = time_t_from_FAT(FAT_from_time_t(t)); - dt = converted_t-t; // disambiguate abs() parameter - TEST(abs(dt) < 2); -} - -static void self_test() -{ - test_fat_timedate_conversion(); -} - -SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/lib/res/graphics/ogl_tex.cpp b/source/lib/res/graphics/ogl_tex.cpp index 577c931be0..acc505ae45 100644 --- a/source/lib/res/graphics/ogl_tex.cpp +++ b/source/lib/res/graphics/ogl_tex.cpp @@ -875,7 +875,7 @@ LibError ogl_tex_get_size(Handle ht, uint* w, uint* h, uint* bpp) } -// retrieve Tex.flags and the corresponding OpenGL format. +// retrieve TexFlags and the corresponding OpenGL format. // the latter is determined during ogl_tex_upload and is 0 before that. // all params are optional and filled if non-NULL. LibError ogl_tex_get_format(Handle ht, uint* flags, GLenum* fmt) diff --git a/source/lib/res/graphics/tex.cpp b/source/lib/res/graphics/tex.cpp index 64b97a0a05..b6e5332e55 100644 --- a/source/lib/res/graphics/tex.cpp +++ b/source/lib/res/graphics/tex.cpp @@ -34,6 +34,10 @@ #include "tex_codec.h" +//----------------------------------------------------------------------------- +// validation +//----------------------------------------------------------------------------- + // be careful not to use other tex_* APIs here because they call us. LibError tex_validate(const Tex* t) { @@ -103,6 +107,54 @@ LibError tex_validate_plain_format(uint bpp, uint flags) } +//----------------------------------------------------------------------------- +// mipmaps +//----------------------------------------------------------------------------- + +void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data, + int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx) +{ + uint level_w = w, level_h = h; + const u8* level_data = data; + + // we iterate through the loop (necessary to skip over image data), + // but do not actually call back until the requisite number of + // levels have been skipped (i.e. level == 0). + int level = -(int)levels_to_skip; + if(levels_to_skip == -1) + level = 0; + + // until at level 1x1: + for(;;) + { + // used to skip past this mip level in + const size_t level_data_size = (size_t)(round_up(level_w, data_padding) * round_up(level_h, data_padding) * bpp/8); + + if(level >= 0) + cb((uint)level, level_w, level_h, level_data, level_data_size, ctx); + + level_data += level_data_size; + + // 1x1 reached - done + if(level_w == 1 && level_h == 1) + break; + level_w /= 2; + level_h /= 2; + // if the texture is non-square, one of the dimensions will become + // 0 before the other. to satisfy OpenGL's expectations, change it + // back to 1. + if(level_w == 0) level_w = 1; + if(level_h == 0) level_h = 1; + level++; + + // special case: no mipmaps, we were only supposed to call for + // the base level + if(levels_to_skip == TEX_BASE_LEVEL_ONLY) + break; + } +} + + struct CreateLevelData { uint num_components; @@ -182,6 +234,34 @@ static void create_level(uint level, uint level_w, uint level_h, } +static LibError add_mipmaps(Tex* t, uint w, uint h, uint bpp, + void* new_data, size_t data_size) +{ + // this code assumes the image is of POT dimension; we don't + // go to the trouble of implementing image scaling because + // the only place this is used (ogl_tex_upload) requires POT anyway. + if(!is_pow2(w) || !is_pow2(h)) + WARN_RETURN(ERR_TEX_INVALID_SIZE); + t->flags |= TEX_MIPMAPS; // must come before tex_img_size! + const size_t mipmap_size = tex_img_size(t); + Handle hm; + const u8* mipmap_data = (const u8*)mem_alloc(mipmap_size, 4*KiB, 0, &hm); + if(!mipmap_data) + WARN_RETURN(ERR_NO_MEM); + CreateLevelData cld = { bpp/8, w, h, (const u8*)new_data, data_size }; + tex_util_foreach_mipmap(w, h, bpp, mipmap_data, 0, 1, create_level, &cld); + mem_free_h(t->hm); + t->hm = hm; + t->ofs = 0; + + return ERR_OK; +} + + +//----------------------------------------------------------------------------- +// pixel format conversion (transformation) +//----------------------------------------------------------------------------- + TIMER_ADD_CLIENT(tc_plain_transform); // handles BGR and row flipping in "plain" format (see below). @@ -289,30 +369,52 @@ TIMER_ACCRUE(tc_plain_transform); t->ofs = 0; if(!(t->flags & TEX_MIPMAPS) && transforms & TEX_MIPMAPS) - { - // this code assumes the image is of POT dimension; we don't - // go to the trouble of implementing image scaling because - // the only place this is used (ogl_tex_upload) requires POT anyway. - if(!is_pow2(w) || !is_pow2(h)) - WARN_RETURN(ERR_TEX_INVALID_SIZE); - t->flags |= TEX_MIPMAPS; // must come before tex_img_size! - const size_t mipmap_size = tex_img_size(t); - Handle hm; - const u8* mipmap_data = (const u8*)mem_alloc(mipmap_size, 4*KiB, 0, &hm); - if(!mipmap_data) - WARN_RETURN(ERR_NO_MEM); - CreateLevelData cld = { bpp/8, w, h, (const u8*)new_data, data_size }; - tex_util_foreach_mipmap(w, h, bpp, mipmap_data, 0, 1, create_level, &cld); - mem_free_h(t->hm); - t->hm = hm; - t->ofs = 0; - } + RETURN_ERR(add_mipmaps(t, w, h, bpp, new_data, data_size)); CHECK_TEX(t); return ERR_OK; } +TIMER_ADD_CLIENT(tc_transform); + +// change 's pixel format by flipping the state of all TEX_* flags +// that are set in transforms. +LibError tex_transform(Tex* t, uint transforms) +{ + TIMER_ACCRUE(tc_transform); + CHECK_TEX(t); + + const uint target_flags = t->flags ^ transforms; + uint remaining_transforms; + for(;;) + { + remaining_transforms = target_flags ^ t->flags; + // we're finished (all required transforms have been done) + if(remaining_transforms == 0) + return ERR_OK; + + LibError ret = tex_codec_transform(t, remaining_transforms); + if(ret != 0) + break; + } + + // last chance + RETURN_ERR(plain_transform(t, remaining_transforms)); + return ERR_OK; +} + + +// change 's pixel format to the new format specified by . +// (note: this is equivalent to tex_transform(t, t->flags^new_flags). +LibError tex_transform_to(Tex* t, uint new_flags) +{ + // tex_transform takes care of validating + const uint transforms = t->flags ^ new_flags; + return tex_transform(t, transforms); +} + + //----------------------------------------------------------------------------- // image orientation //----------------------------------------------------------------------------- @@ -369,55 +471,7 @@ bool tex_orientations_match(uint src_flags, uint dst_orientation) //----------------------------------------------------------------------------- -// util -//----------------------------------------------------------------------------- - -void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data, - int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx) -{ - uint level_w = w, level_h = h; - const u8* level_data = data; - - // we iterate through the loop (necessary to skip over image data), - // but do not actually call back until the requisite number of - // levels have been skipped (i.e. level == 0). - int level = -(int)levels_to_skip; - if(levels_to_skip == -1) - level = 0; - - // until at level 1x1: - for(;;) - { - // used to skip past this mip level in - const size_t level_data_size = (size_t)(round_up(level_w, data_padding) * round_up(level_h, data_padding) * bpp/8); - - if(level >= 0) - cb((uint)level, level_w, level_h, level_data, level_data_size, ctx); - - level_data += level_data_size; - - // 1x1 reached - done - if(level_w == 1 && level_h == 1) - break; - level_w /= 2; - level_h /= 2; - // if the texture is non-square, one of the dimensions will become - // 0 before the other. to satisfy OpenGL's expectations, change it - // back to 1. - if(level_w == 0) level_w = 1; - if(level_h == 0) level_h = 1; - level++; - - // special case: no mipmaps, we were only supposed to call for - // the base level - if(levels_to_skip == -1) - break; - } -} - - -//----------------------------------------------------------------------------- -// API +// misc. API //----------------------------------------------------------------------------- // indicate if 's extension is that of a texture format @@ -440,78 +494,6 @@ bool tex_is_known_extension(const char* filename) } -// split out of tex_load to ease resource cleanup -static LibError tex_load_impl(FileIOBuf file_, size_t file_size, Tex* t) -{ - u8* file = (u8*)file_; - const TexCodecVTbl* c; - RETURN_ERR(tex_codec_for_header(file, file_size, &c)); - - // make sure the entire header has been read - const size_t min_hdr_size = c->hdr_size(0); - if(file_size < min_hdr_size) - WARN_RETURN(ERR_INCOMPLETE_HEADER); - const size_t hdr_size = c->hdr_size(file); - if(file_size < hdr_size) - WARN_RETURN(ERR_INCOMPLETE_HEADER); - t->ofs = hdr_size; - - DynArray da; - RETURN_ERR(da_wrap_fixed(&da, file, file_size)); - - RETURN_ERR(c->decode(&da, t)); - - (void)da_free(&da); // for completeness only; just zeros - - // sanity checks - if(!t->w || !t->h || t->bpp > 32) - WARN_RETURN(ERR_TEX_FMT_INVALID); - // .. note: decode() may have decompressed the image; cannot use file_size. - size_t hm_size; - (void)mem_get_ptr(t->hm, &hm_size); - if(hm_size < t->ofs + tex_img_size(t)) - WARN_RETURN(ERR_TEX_INVALID_SIZE); - - flip_to_global_orientation(t); - - return ERR_OK; -} - - -// MEM_DTOR -> file_buf_free adapter (used for mem_wrap-ping FileIOBuf) -static void file_buf_dtor(void* p, size_t UNUSED(size), uintptr_t UNUSED(ctx)) -{ - (void)file_buf_free((FileIOBuf)p); -} - -// load the specified image from file into the given Tex object. -// currently supports BMP, TGA, JPG, JP2, PNG, DDS. -LibError tex_load(const char* fn, Tex* t, uint file_flags) -{ - // load file - FileIOBuf file; size_t file_size; - // rationale: we need the Handle return value for Tex.hm - the data pointer - // must be protected against being accidentally free-d in that case. - - RETURN_ERR(vfs_load(fn, file, file_size, file_flags)); - Handle hm = mem_wrap((void*)file, file_size, 0, 0, 0, file_buf_dtor, 0, (void*)tex_load); - t->hm = hm; - LibError ret = tex_load_impl(file, file_size, t); - if(ret < 0) - { - (void)tex_free(t); - debug_warn("failed"); - return ret; - } - - // do not free hm! it either still holds the image data (i.e. texture - // wasn't compressed) or was replaced by a new buffer for the image data. - - CHECK_TEX(t); - return ERR_OK; -} - - // store the given image data into a Tex object; this will be as if // it had been loaded via tex_load. // @@ -566,46 +548,7 @@ LibError tex_free(Tex* t) //----------------------------------------------------------------------------- - -TIMER_ADD_CLIENT(tc_transform); - -// change 's pixel format by flipping the state of all TEX_* flags -// that are set in transforms. -LibError tex_transform(Tex* t, uint transforms) -{ -TIMER_ACCRUE(tc_transform); - CHECK_TEX(t); - - const uint target_flags = t->flags ^ transforms; - uint remaining_transforms; - for(;;) - { - remaining_transforms = target_flags ^ t->flags; - // we're finished (all required transforms have been done) - if(remaining_transforms == 0) - return ERR_OK; - - LibError ret = tex_codec_transform(t, remaining_transforms); - if(ret != 0) - break; - } - - // last chance - RETURN_ERR(plain_transform(t, remaining_transforms)); - return ERR_OK; -} - - -// change 's pixel format to the new format specified by . -// (note: this is equivalent to tex_transform(t, t->flags^new_flags). -LibError tex_transform_to(Tex* t, uint new_flags) -{ - // tex_transform takes care of validating - const uint transforms = t->flags ^ new_flags; - return tex_transform(t, transforms); -} - - +// getters //----------------------------------------------------------------------------- // returns a pointer to the image data (pixels), taking into account any @@ -646,8 +589,6 @@ size_t tex_img_size(const Tex* t) } -//----------------------------------------------------------------------------- - // return the minimum header size (i.e. offset to pixel data) of the // file format indicated by 's extension (that is all it need contain: // e.g. ".bmp"). returns 0 on error (i.e. no codec found). @@ -663,10 +604,58 @@ size_t tex_hdr_size(const char* fn) } -// write the specified texture to disk. -// note: cannot be made const because the image may have to be -// transformed to write it out in the format determined by 's extension. -LibError tex_write(Tex* t, const char* fn) +//----------------------------------------------------------------------------- +// read/write from memory and disk +//----------------------------------------------------------------------------- + +LibError tex_decode(const u8* data, size_t data_size, MEM_DTOR dtor, Tex* t) +{ + const TexCodecVTbl* c; + RETURN_ERR(tex_codec_for_header(data, data_size, &c)); + + // make sure the entire header is available + const size_t min_hdr_size = c->hdr_size(0); + if(data_size < min_hdr_size) + WARN_RETURN(ERR_INCOMPLETE_HEADER); + const size_t hdr_size = c->hdr_size(data); + if(data_size < hdr_size) + WARN_RETURN(ERR_INCOMPLETE_HEADER); + + // wrap pointer into a Handle; required for Tex.hm. + // rationale: a Handle protects the texture memory from being + // accidentally free-d. + Handle hm = mem_wrap((void*)data, data_size, 0, 0, 0, dtor, 0, (void*)tex_decode); + + t->hm = hm; + t->ofs = hdr_size; + + // for orthogonality, encode and decode both receive the memory as a + // DynArray. package data into one and free it again after decoding: + DynArray da; + RETURN_ERR(da_wrap_fixed(&da, (u8*)data, data_size)); + + RETURN_ERR(c->decode(&da, t)); + + // note: not reached if decode fails. that's not a problem; + // this call just zeroes and could be left out. + (void)da_free(&da); + + // sanity checks + if(!t->w || !t->h || t->bpp > 32) + WARN_RETURN(ERR_TEX_FMT_INVALID); + // .. note: can't use data_size - decode may have decompressed the image. + size_t hm_size; + (void)mem_get_ptr(t->hm, &hm_size); + if(hm_size < t->ofs + tex_img_size(t)) + WARN_RETURN(ERR_TEX_INVALID_SIZE); + + flip_to_global_orientation(t); + + return ERR_OK; +} + + +LibError tex_encode(Tex* t, const char* fn, DynArray* da) { CHECK_TEX(t); CHECK_ERR(tex_validate_plain_format(t->bpp, t->flags)); @@ -676,31 +665,74 @@ LibError tex_write(Tex* t, const char* fn) // most likely the case if in_img == + c->hdr_size(0). // this would make for zero-copy IO. - DynArray da; const size_t max_out_size = tex_img_size(t)*4 + 256*KiB; - RETURN_ERR(da_alloc(&da, max_out_size)); + RETURN_ERR(da_alloc(da, max_out_size)); const TexCodecVTbl* c; CHECK_ERR(tex_codec_for_filename(fn, &c)); // encode into - LibError err = c->encode(t, &da); + LibError err = c->encode(t, da); if(err < 0) { - debug_printf("%s (%s) failed: %d", __func__, c->name, err); - debug_warn("failed"); - goto fail; + (void)da_free(da); + WARN_RETURN(err); } + return ERR_OK; +} + + + +// MEM_DTOR -> file_buf_free adapter (used for mem_wrap-ping FileIOBuf) +static void file_buf_dtor(void* p, size_t UNUSED(size), uintptr_t UNUSED(ctx)) +{ + (void)file_buf_free((FileIOBuf)p); +} + +// load the specified image from file into the given Tex object. +// currently supports BMP, TGA, JPG, JP2, PNG, DDS. +LibError tex_load(const char* fn, Tex* t, uint file_flags) +{ + // load file + FileIOBuf file; size_t file_size; + RETURN_ERR(vfs_load(fn, file, file_size, file_flags)); + + LibError ret = tex_decode(file, file_size, file_buf_dtor, t); + if(ret < 0) + { + (void)tex_free(t); + WARN_RETURN(ret); + } + + // do not free hm! it either still holds the image data (i.e. texture + // wasn't compressed) or was replaced by a new buffer for the image data. + + CHECK_TEX(t); + return ERR_OK; +} + + +// write the specified texture to disk. +// note: cannot be made const because the image may have to be +// transformed to write it out in the format determined by 's extension. +LibError tex_write(Tex* t, const char* fn) +{ + DynArray da; + RETURN_ERR(tex_encode(t, fn, &da)); + // write to disk + LibError ret = ERR_OK; { const size_t sector_aligned_size = round_up(da.cur_size, file_sector_size); (void)da_set_size(&da, sector_aligned_size); const ssize_t bytes_written = vfs_store(fn, da.base, da.pos); - debug_assert(bytes_written == (ssize_t)da.pos); + if(bytes_written > 0) + debug_assert(bytes_written == (ssize_t)da.pos); + else + ret = (LibError)bytes_written; } -fail: (void)da_free(&da); - return err; + return ret; } diff --git a/source/lib/res/graphics/tex.h b/source/lib/res/graphics/tex.h index 65597394a6..5ac24a11af 100644 --- a/source/lib/res/graphics/tex.h +++ b/source/lib/res/graphics/tex.h @@ -10,7 +10,7 @@ */ /* - * Copyright (c) 2004 Jan Wassenberg + * Copyright (c) 2004-2005 Jan Wassenberg * * Redistribution and/or modification are also permitted under the * terms of the GNU General Public License as published by the @@ -21,7 +21,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ -/* +/** Introduction ------------ @@ -55,7 +55,7 @@ close to the final pixel format. 1) one of the exceptions is S3TC compressed textures. glCompressedTexImage2D requires these be passed in their original format; decompressing would be - counterproductive. In this and similar cases, Tex.flags indicates such + counterproductive. In this and similar cases, TexFlags indicates such deviations from the plain format. @@ -95,92 +95,124 @@ This supports external libraries like libpng that do not know the output size beforehand, but avoids the need for a buffer between library and IO layer. Read and write are zero-copy. -*/ +**/ #ifndef TEX_H__ #define TEX_H__ #include "../handle.h" -// flags describing the pixel format. these are to be interpreted as -// deviations from "plain" format, i.e. uncompressed RGB. +/** + * flags describing the pixel format. these are to be interpreted as + * deviations from "plain" format, i.e. uncompressed RGB. + **/ enum TexFlags { - // flags & TEX_DXT is a field indicating compression. - // if 0, the texture is uncompressed; - // otherwise, it holds the S3TC type: 1,3,5 or DXT1A. - // not converted by default - glCompressedTexImage2D receives - // the compressed data. - TEX_DXT = 0x7, // mask - // we need a special value for DXT1a to avoid having to consider - // flags & TEX_ALPHA to determine S3TC type. - // the value is arbitrary; do not rely on it! + /** + * flags & TEX_DXT is a field indicating compression. + * if 0, the texture is uncompressed; + * otherwise, it holds the S3TC type: 1,3,5 or DXT1A. + * not converted by default - glCompressedTexImage2D receives + * the compressed data. + **/ + TEX_DXT = 0x7, // mask + + /** + * we need a special value for DXT1a to avoid having to consider + * flags & TEX_ALPHA to determine S3TC type. + * the value is arbitrary; do not rely on it! + **/ DXT1A = 7, - // indicates B and R pixel components are exchanged. depending on - // flags & TEX_ALPHA or bpp, this means either BGR or BGRA. - // not converted by default - it's an acceptable format for OpenGL. + /** + * indicates B and R pixel components are exchanged. depending on + * flags & TEX_ALPHA or bpp, this means either BGR or BGRA. + * not converted by default - it's an acceptable format for OpenGL. + **/ TEX_BGR = 0x08, - // indicates the image contains an alpha channel. this is set for - // your convenience - there are many formats containing alpha and - // divining this information from them is hard. - // (conversion is not applicable here) + /** + * indicates the image contains an alpha channel. this is set for + * your convenience - there are many formats containing alpha and + * divining this information from them is hard. + * (conversion is not applicable here) + **/ TEX_ALPHA = 0x10, - // indicates the image is 8bpp greyscale. this is required to - // differentiate between alpha-only and intensity formats. - // not converted by default - it's an acceptable format for OpenGL. + /** + * indicates the image is 8bpp greyscale. this is required to + * differentiate between alpha-only and intensity formats. + * not converted by default - it's an acceptable format for OpenGL. + **/ TEX_GREY = 0x20, - // flags & TEX_ORIENTATION is a field indicating orientation, - // i.e. in what order the pixel rows are stored. - // - // tex_load always sets this to the global orientation - // (and flips the image accordingly). - // texture codecs may in intermediate steps during loading set this - // to 0 if they don't know which way around they are (e.g. DDS), - // or to whatever their file contains. + /** + * flags & TEX_ORIENTATION is a field indicating orientation, + * i.e. in what order the pixel rows are stored. + * + * tex_load always sets this to the global orientation + * (and flips the image accordingly to match). + * texture codecs may in intermediate steps during loading set this + * to 0 if they don't know which way around they are (e.g. DDS), + * or to whatever their file contains. + **/ TEX_BOTTOM_UP = 0x40, TEX_TOP_DOWN = 0x80, - TEX_ORIENTATION = TEX_BOTTOM_UP|TEX_TOP_DOWN, // mask + TEX_ORIENTATION = TEX_BOTTOM_UP|TEX_TOP_DOWN, /// mask - // indicates the image data includes mipmaps. they are stored from lowest - // to highest (1x1), one after the other. - // (conversion is not applicable here) + /** + * indicates the image data includes mipmaps. they are stored from lowest + * to highest (1x1), one after the other. + * (conversion is not applicable here) + **/ TEX_MIPMAPS = 0x100 }; -// stores all data describing an image. -// we try to minimize size, since this is stored in OglTex resources -// (which are big and pushing the h_mgr limit). +/** + * stores all data describing an image. + * we try to minimize size, since this is stored in OglTex resources + * (which are big and pushing the h_mgr limit). + **/ struct Tex { - // H_Mem handle to image data. note: during the course of transforms - // (which may occur when being loaded), this may be replaced with - // a Handle to a new buffer (e.g. if decompressing file contents). + /** + * H_Mem handle to image data. note: during the course of transforms + * (which may occur when being loaded), this may be replaced with + * a Handle to a new buffer (e.g. if decompressing file contents). + **/ Handle hm; - // offset to image data in file. this is required since - // tex_get_data needs to return the pixels, but mem_get_ptr(hm) - // returns the actual file buffer. zero-copy load and - // write-back to file is also made possible. + /** + * offset to image data in file. this is required since + * tex_get_data needs to return the pixels, but mem_get_ptr(hm) + * returns the actual file buffer. zero-copy load and + * write-back to file is also made possible. + **/ size_t ofs; uint w : 16; uint h : 16; uint bpp : 16; - // see TexFlags and "Format Conversion" in docs. + /// see TexFlags and "Format Conversion" in docs. uint flags : 16; }; -// set the orientation (either TEX_BOTTOM_UP or TEX_TOP_DOWN) to which -// all loaded images will automatically be converted -// (excepting file formats that don't specify their orientation, i.e. DDS). -// see "Default Orientation" in docs. +/** + * is the texture object valid and self-consistent? + * @return LibError + **/ +extern LibError tex_validate(const Tex* t); + + +/** + * set the orientation to which all loaded images will + * automatically be converted (excepting file formats that don't specify + * their orientation, i.e. DDS). see "Default Orientation" in docs. + * @param orientation either TEX_BOTTOM_UP or TEX_TOP_DOWN + **/ extern void tex_set_global_orientation(int orientation); @@ -188,38 +220,51 @@ extern void tex_set_global_orientation(int orientation); // open/close // -// indicate if 's extension is that of a texture format -// supported by tex_load. case-insensitive. -// -// rationale: tex_load complains if the given file is of an -// unsupported type. this API allows users to preempt that warning -// (by checking the filename themselves), and also provides for e.g. -// enumerating only images in a file picker. -// an alternative might be a flag to suppress warning about invalid files, -// but this is open to misuse. -extern bool tex_is_known_extension(const char* filename); - -// load the specified image from file into the given Tex object. -// currently supports BMP, TGA, JPG, JP2, PNG, DDS. +/** + * load the specified image from file into a Tex object. + * + * FYI, currently BMP, TGA, JPG, JP2, PNG, DDS are supported - but don't + * rely on this (not all codecs may be included). + * + * @param fn filename + * @param t output texture object + * @param file_flags additional flags for vfs_load + * @return LibError + **/ extern LibError tex_load(const char* fn, Tex* t, uint file_flags = 0); -// store the given image data into a Tex object; this will be as if -// it had been loaded via tex_load. -// -// rationale: support for in-memory images is necessary for -// emulation of glCompressedTexImage2D and useful overall. -// however, we don't want to provide an alternate interface for each API; -// these would have to be changed whenever fields are added to Tex. -// instead, provide one entry point for specifying images. -// note: since we do not know how was allocated, the caller must do -// so (after calling tex_free, which is required regardless of alloc type). -// -// we need only add bookkeeping information and "wrap" it in -// our Tex struct, hence the name. +/** + * store the given image data into a Tex object; this will be as if + * it had been loaded via tex_load. + * + * rationale: support for in-memory images is necessary for + * emulation of glCompressedTexImage2D and useful overall. + * however, we don't want to provide an alternate interface for each API; + * these would have to be changed whenever fields are added to Tex. + * instead, provide one entry point for specifying images. + * note: since we do not know how was allocated, the caller must free + * it themselves (after calling tex_free, which is required regardless of + * alloc type). + * + * we need only add bookkeeping information and "wrap" it in + * our Tex struct, hence the name. + * + * @param w, h pixel dimensions + * @param bpp bits per pixel + * @param flags TexFlags + * @param img texture data. note: size is calculated from other params. + * @param t output texture object. + * @return LibError + **/ extern LibError tex_wrap(uint w, uint h, uint bpp, uint flags, void* img, Tex* t); -// free all resources associated with the image and make further -// use of it impossible. +/** + * free all resources associated with the image and make further + * use of it impossible. + * + * @param t texture object (note: not zeroed afterwards; see impl) + * @return LibError + **/ extern LibError tex_free(Tex* t); @@ -227,12 +272,21 @@ extern LibError tex_free(Tex* t); // modify image // -// change 's pixel format by flipping the state of all TEX_* flags -// that are set in transforms. +/** + * change 's pixel format. + * + * @param transforms TexFlags that are to be flipped. + * @return LibError + **/ extern LibError tex_transform(Tex* t, uint transforms); -// change 's pixel format to the new format specified by . -// (note: this is equivalent to tex_transform(t, t->flags^new_flags). +/** + * change 's pixel format (2nd version) + * (note: this is equivalent to tex_transform(t, t->flags^new_flags). + * + * @param new_flags desired new value of TexFlags. + * @return LibError + **/ extern LibError tex_transform_to(Tex* t, uint new_flags); @@ -240,65 +294,115 @@ extern LibError tex_transform_to(Tex* t, uint new_flags); // return image information // -// since Tex is a struct, its fields are accessible to callers. -// this is more for C compatibility than convenience; the following should -// be used instead of direct access to the corresponding fields because -// they take care of some dirty work. +/** + * rationale: since Tex is a struct, its fields are accessible to callers. + * this is more for C compatibility than convenience; the following should + * be used instead of direct access to the corresponding fields because + * they take care of some dirty work. + **/ -// returns a pointer to the image data (pixels), taking into account any -// header(s) that may come before it. see Tex.hm comment above. +/** + * return a pointer to the image data (pixels), taking into account any + * header(s) that may come before it. see Tex.hm comment above. + * + * @param t input texture object + * @return pointer to data returned by mem_get_ptr (holds reference)! + **/ extern u8* tex_get_data(const Tex* t); -// return total byte size of the image pixels. (including mipmaps!) -// this is preferable to calculating manually because it's -// less error-prone (e.g. confusing bits_per_pixel with bytes). +/** + * return total byte size of the image pixels. (including mipmaps!) + * rationale: this is preferable to calculating manually because it's + * less error-prone (e.g. confusing bits_per_pixel with bytes). + * + * @param t input texture object + * @return size [bytes] + **/ extern size_t tex_img_size(const Tex* t); +/** + * special value for levels_to_skip: the callback will only be called + * for the base mipmap level (i.e. 100%) + **/ +const int TEX_BASE_LEVEL_ONLY = -1; + +/** + * callback function for each mipmap level. + * + * @param level number; 0 for base level (i.e. 100%), or the first one + * in case some were skipped. + * @param level_w, level_h pixel dimensions (powers of 2, never 0) + * @param level_data the level's texels + * @param level_data_size [bytes] + * @param ctx passed through from tex_util_foreach_mipmap. + **/ +typedef void (*MipmapCB)(uint level, uint level_w, uint level_h, + const u8* level_data, size_t level_data_size, void* ctx); + +/** + * for a series of mipmaps stored from base to highest, call back for + * each level. + * + * @param w, h pixel dimensions + * @param bpp bits per pixel + * @param data series of mipmaps + * @param levels_to_skip number of levels (counting from base) to skip, or + * TEX_BASE_LEVEL_ONLY to only call back for the base image. + * rationale: this avoids needing to special case for images with or + * without mipmaps. + * @param data_padding minimum pixel dimensions of mipmap levels. + * this is used in S3TC images, where each level is actually stored in + * 4x4 blocks. usually 1 to indicate levels are consecutive. + * @param cb MipmapCB to call + * @param ctx extra data to pass to cb + **/ +extern void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data, + int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx); + + // // image writing // -// return the minimum header size (i.e. offset to pixel data) of the -// file format indicated by 's extension (that is all it need contain: -// e.g. ".bmp"). returns 0 on error (i.e. no codec found). -// this can be used to optimize calls to tex_write: when allocating the -// buffer that will hold the image, allocate this much extra and -// pass the pointer as base+hdr_size. this allows writing the header -// directly into the output buffer and makes for zero-copy IO. +/** + * is the file's extension that of a texture format supported by tex_load? + * + * rationale: tex_load complains if the given file is of an + * unsupported type. this API allows users to preempt that warning + * (by checking the filename themselves), and also provides for e.g. + * enumerating only images in a file picker. + * an alternative might be a flag to suppress warning about invalid files, + * but this is open to misuse. + * + * @param filename only the extension (that after '.') is used. case-insensitive. + * @return bool + **/ +extern bool tex_is_known_extension(const char* filename); + +/** + * return the minimum header size (i.e. offset to pixel data) of the + * file format corresponding to the filename. + * + * rationale: this can be used to optimize calls to tex_write: when + * allocating the buffer that will hold the image, allocate this much + * extra and pass the pointer as base+hdr_size. this allows writing the + * header directly into the output buffer and makes for zero-copy IO. + * + * @param fn filename; only the extension (that after '.') is used. + * case-insensitive. + * @return size [bytes] or 0 on error (i.e. no codec found). + **/ extern size_t tex_hdr_size(const char* fn); -// write the specified texture to disk. -// note: cannot be made const because the image may have to be -// transformed to write it out in the format determined by 's extension. +/** + * write the specified texture to disk. + * + * @param t input texture object. note: cannot be made const because the + * image may have to be transformed to write it out in the format + * determined by 's extension. + * @return LibError + **/ extern LibError tex_write(Tex* t, const char* fn); - -// internal use only: -extern LibError tex_validate(const Tex* t); - -// check if the given texture format is acceptable: 8bpp grey, -// 24bpp color or 32bpp color+alpha (BGR / upside down are permitted). -// basically, this is the "plain" format understood by all codecs and -// tex_codec_plain_transform. -extern LibError tex_validate_plain_format(uint bpp, uint flags); - - -// indicate if the orientation specified by matches -// dst_orientation (if the latter is 0, then the global_orientation). -// (we ask for src_flags instead of src_orientation so callers don't -// have to mask off TEX_ORIENTATION) -extern bool tex_orientations_match(uint src_flags, uint dst_orientation); - -typedef void (*MipmapCB)(uint level, uint level_w, uint level_h, - const u8* level_data, size_t level_data_size, void* ctx); - -// special value for levels_to_skip: the callback will only be called -// for the base mipmap level (i.e. 100%) -const int TEX_BASE_LEVEL_ONLY = -1; - -extern void tex_util_foreach_mipmap(uint w, uint h, uint bpp, const u8* restrict data, - int levels_to_skip, uint data_padding, MipmapCB cb, void* restrict ctx); - - -#endif // TEX_H__ +#endif // TEX_H__ diff --git a/source/lib/res/graphics/tex_codec.cpp b/source/lib/res/graphics/tex_codec.cpp index ebb6a02f1f..cdd5ed6ece 100644 --- a/source/lib/res/graphics/tex_codec.cpp +++ b/source/lib/res/graphics/tex_codec.cpp @@ -85,6 +85,17 @@ LibError tex_codec_for_header(const u8* file, size_t file_size, const TexCodecVT } +const TexCodecVTbl* tex_codec_next(const TexCodecVTbl* prev_codec) +{ + // first time + if(!prev_codec) + return codecs; + // middle of list: return next (can be 0 to indicate end of list) + else + return prev_codec->next; +} + + LibError tex_codec_transform(Tex* t, uint transforms) { LibError ret = INFO_TEX_CODEC_CANNOT_HANDLE; diff --git a/source/lib/res/graphics/tex_codec.h b/source/lib/res/graphics/tex_codec.h index 310c9d25be..73db3f0bf6 100644 --- a/source/lib/res/graphics/tex_codec.h +++ b/source/lib/res/graphics/tex_codec.h @@ -24,89 +24,219 @@ #define TEX_CODEC_H__ #include "tex.h" +#include "tex_internal.h" // for codec's convenience #include "lib/allocators.h" -// rationale: no C++ to allow us to store const char* name in vtbl. - +/** + * virtual method table for TexCodecs. + * rationale: this works in C and also allows storing name and next in vtbl. + * 'template method'-style interface to increase code reuse and + * simplify writing new codecs. + **/ struct TexCodecVTbl { - // 'template method' to increase code reuse and simplify writing new codecs + /** + * decode the file into a Tex structure. + * + * @param da input data array (not const, because the texture + * may have to be flipped in-place - see "texture orientation"). + * its size is guaranteed to be >= 4. + * (usually enough to compare the header's "magic" field; + * anyway, no legitimate file will be smaller) + * @param t output texture object + * @return LibError + **/ + LibError (*decode)(DynArray * restrict da, Tex * restrict t); - // pointers aren't const, because the textures - // may have to be flipped in-place - see "texture orientation". - // size is guaranteed to be >= 4. - // (usually enough to compare the header's "magic" field; - // anyway, no legitimate file will be smaller) - LibError (*decode)(DynArray* restrict da, Tex* restrict t); - // rationale: some codecs cannot calculate the output size beforehand - // (e.g. PNG output via libpng); we therefore require each one to - // allocate memory itself and return the pointer. - // - // note: cannot be made const because encoding may require a - // tex_transform. - LibError (*encode)(Tex* restrict t, DynArray* restrict da); + /** + * encode the texture data into the codec's file format (in memory). + * + * @param t input texture object. note: non-const because encoding may + * require a tex_transform. + * @param da output data array, allocated by codec. + * rationale: some codecs cannot calculate the output size beforehand + * (e.g. PNG output via libpng), so the output memory cannot be allocated + * by the caller. + * @return LibError + **/ + LibError (*encode)(Tex * restrict t, DynArray * restrict da); + /** + * transform the texture's pixel format. + * + * @param t texture object + * @param transforms: OR-ed combination of TEX_* flags that are to + * be changed. note: the codec needs only handle situations specific + * to its format; generic pixel format transforms are handled by + * the caller. + **/ LibError (*transform)(Tex* t, uint transforms); - // only guaranteed 4 bytes! - bool (*is_hdr)(const u8* file); + /** + * indicate if the data appears to be an instance of this codec's header, + * i.e. can this codec decode it? + * + * @param file input data; only guaranteed to be 4 bytes! + * (this should be enough to examine the header's 'magic' field) + * @return bool + **/ + bool (*is_hdr)(const u8 * file); - // precondition: ext is valid string - // ext doesn't include '.'; just compare against e.g. "png" - // must compare case-insensitive! + /** + * is the extension that of a file format supported by this codec? + * + * rationale: cannot just return the extension string and have + * caller compare it (-> smaller code) because a codec's file format + * may have several valid extensions (e.g. jpg and jpeg). + * + * @param ext non-NULL extension string; does not contain '.'. + * must be compared as case-insensitive. + * @return bool + **/ bool (*is_ext)(const char* ext); + /** + * return size of the file header supported by this codec. + * + * @param file the specific header to return length of (taking its + * variable-length fields into account). if NULL, return minimum + * guaranteed header size, i.e. the header without any + * variable-length fields. + * @return size [bytes] + **/ size_t (*hdr_size)(const u8* file); + /** + * name of codec for debug purposes. typically set via TEX_CODEC_REGISTER. + **/ const char* name; - // intrusive linked-list of codecs: more convenient than fixed-size - // static storage. + /** + * intrusive linked-list of codecs: more convenient than fixed-size + * static storage. + * set by caller; should be initialized to NULL. + **/ const TexCodecVTbl* next; }; +/** + * build codec vtbl and register it. the codec will be queried for future + * texture load requests. call order is undefined, but since each codec + * only steps up if it can handle the given format, this is not a problem. + * + * @param name identifier of codec (not string!). used to bind 'member' + * functions prefixed with it to the vtbl, and as the TexCodecVTbl name. + * it should also mirror the default file extension (e.g. dds) - + * this is relied upon (but verified) in the self-test. + * + * usage: at file scope within the source file containing the codec's methods. + **/ #define TEX_CODEC_REGISTER(name)\ - static TexCodecVTbl vtbl = { name##_decode, name##_encode, name##_transform, name##_is_hdr, name##_is_ext, name##_hdr_size, #name};\ + static TexCodecVTbl vtbl = \ + {\ + name##_decode, name##_encode, name##_transform,\ + name##_is_hdr, name##_is_ext, name##_hdr_size,\ + #name\ + };\ static int dummy = tex_codec_register(&vtbl); -// add this vtbl to the codec list. called at NLSO init time by the -// TEX_CODEC_REGISTER in each codec file. note that call order and therefore -// order in the list is undefined, but since each codec only steps up if it -// can handle the given format, this is not a problem. -// -// returns int to alloc calling from a macro at file scope. +/** + * add this vtbl to the codec list. called at NLSO init time by the + * TEX_CODEC_REGISTER in each codec file. + * order in list is unspecified; see TEX_CODEC_REGISTER. + * + * @param c pointer to vtbl. + * @return int (allows calling from a macro at file scope; value is not used) + **/ extern int tex_codec_register(TexCodecVTbl* c); -// find codec that recognizes the desired output file extension, -// or return ERR_UNKNOWN_FORMAT if unknown. -// note: does not raise a warning because it is used by -// tex_is_known_extension. +/** + * find codec that recognizes the desired output file extension. + * + * @param fn filename; only the extension (that after '.') is used. + * case-insensitive. + * @param c (out) vtbl of responsible codec + * @return LibError; ERR_UNKNOWN_FORMAT (without warning, because this is + * called by tex_is_known_extension) if no codec indicates they can + * handle the given extension. + **/ extern LibError tex_codec_for_filename(const char* fn, const TexCodecVTbl** c); -// find codec that recognizes the header's magic field -extern LibError tex_codec_for_header(const u8* file, size_t file_size, const TexCodecVTbl** c); +/** + * find codec that recognizes the header's magic field. + * + * @param data typically contents of file, but need only include the + * (first 4 bytes of) header. + * @param data_size [bytes] + * @param c (out) vtbl of responsible codec + * @return LibError; ERR_UNKNOWN_FORMAT if no codec indicates they can + * handle the given format (header). + **/ +extern LibError tex_codec_for_header(const u8* data, size_t data_size, const TexCodecVTbl** c); +/** + * enumerate all registered codecs. + * + * used by self-test to test each one of them in turn. + * + * @param prev_codec the last codec returned by this function. + * pass 0 the first time. + * note: this routine is stateless and therefore reentrant. + * @return the next codec, or 0 if all have been returned. + **/ +extern const TexCodecVTbl* tex_codec_next(const TexCodecVTbl* prev_codec); + +/** + * transform the texture's pixel format. + * tries each codec's transform method once, or until one indicates success. + * + * @param t texture object + * @param transforms: OR-ed combination of TEX_* flags that are to + * be changed. + * @return LibError + **/ extern LibError tex_codec_transform(Tex* t, uint transforms); - -// allocate an array of row pointers that point into the given texture data. -// indicates whether the file format is top-down or -// bottom-up; the row array is inverted if necessary to match global -// orienatation. (this is more efficient than "transforming" later) -// -// used by PNG and JPG codecs; caller must free() rows when done. -// -// note: we don't allocate the data param ourselves because this function is -// needed for encoding, too (where data is already present). +/** + * allocate an array of row pointers that point into the given texture data. + * for texture decoders that support output via row pointers (e.g. PNG), + * this allows flipping the image vertically (useful when matching bottom-up + * textures to a global orientation) directly, which is much more + * efficient than transforming later via copying all pixels. + * + * @param data the texture data into which row pointers will point. + * note: we don't allocate it here because this function is + * needed for encoding, too (where data is already present). + * @param h height [pixels] of texture. + * @param pitch size [bytes] of one texture row, i.e. width*bytes_per_pixel. + * @param src_flags TexFlags of source texture. used to extract its + * orientation. + * @param dst_orientation desired orientation of the output data. + * can be one of TEX_BOTTOM_UP, TEX_TOP_DOWN, or 0 for the + * "global orientation". + * depending on src and dst, the row array is flipped if necessary. + * @param rows (out) array of row pointers; caller must free() it when done. + * @return LibError + **/ typedef const u8* RowPtr; typedef RowPtr* RowArray; extern LibError tex_codec_alloc_rows(const u8* data, size_t h, size_t pitch, uint src_flags, uint dst_orientation, RowArray& rows); +/** + * apply transforms and then copy header and image into output buffer. + * + * @param t input texture object + * @param transforms transformations to be applied to pixel format + * @param hdr header data + * @param hdr_size [bytes] + * @param da output data array (will be expanded as necessary) + * @return LibError + **/ extern LibError tex_codec_write(Tex* t, uint transforms, const void* hdr, size_t hdr_size, DynArray* da); -#endif // #ifndef TEX_CODEC_H__ +#endif // #ifndef TEX_CODEC_H__ diff --git a/source/lib/res/graphics/tex_internal.h b/source/lib/res/graphics/tex_internal.h new file mode 100644 index 0000000000..7d5a9088b2 --- /dev/null +++ b/source/lib/res/graphics/tex_internal.h @@ -0,0 +1,85 @@ +/** + * ========================================================================= + * File : tex_internal.h + * Project : 0 A.D. + * Description : private texture loader helper functions + * + * @author Jan.Wassenberg@stud.uni-karlsruhe.de + * ========================================================================= + */ + +/* + * Copyright (c) 2006 Jan Wassenberg + * + * Redistribution and/or modification are also permitted under the + * terms of the GNU General Public License as published by the + * Free Software Foundation (version 2 or later, at your option). + * + * This program 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. + */ + +#ifndef TEX_INTERNAL_H__ +#define TEX_INTERNAL_H__ + +#include "../mem.h" // MEM_DTOR +#include "lib/allocators.h" // DynArray + +/** + * check if the given texture format is acceptable: 8bpp grey, + * 24bpp color or 32bpp color+alpha (BGR / upside down are permitted). + * basically, this is the "plain" format understood by all codecs and + * tex_codec_plain_transform. + * @param bpp bits per pixel + * @param flags TexFlags + * @return LibError + **/ +extern LibError tex_validate_plain_format(uint bpp, uint flags); + + +/** + * indicate if the two vertical orientations match. + * + * used by tex_codec. + * + * @param src_flags TexFlags, used to extract the orientation. + * we ask for this instead of src_orientation so callers don't have to + * mask off TEX_ORIENTATION. + * @param dst_orientation orientation to compare against. + * can be one of TEX_BOTTOM_UP, TEX_TOP_DOWN, or 0 for the + * "global orientation". + * @return bool + **/ +extern bool tex_orientations_match(uint src_flags, uint dst_orientation); + + +/** + * decode an in-memory texture file into texture object. + * + * split out of tex_load to ease resource cleanup and allow + * decoding images without needing to write out to disk. + * + * @param data input data + * @param data_size its size [bytes] + * @param dtor the function used to release it when the texture object is + * freed (can be NULL). note: this is necessary because the Tex object + * assumes ownership (necessary due to Tex.hm). + * @param t output texture object. + * @return LibError. + **/ +extern LibError tex_decode(const u8* data, size_t data_size, MEM_DTOR dtor, Tex* t); + +/** + * encode a texture into a memory buffer in the desired file format. + * + * @param t input texture object + * @param fn filename; only used to determine the desired file format + * (via extension) + * @param da output memory array. allocated here; caller must free it + * when no longer needed. invalid unless function succeeds. + * @return LibError + **/ +extern LibError tex_encode(Tex* t, const char* fn, DynArray* da); + +#endif // #ifndef TEX_INTERNAL_H__ diff --git a/source/lib/res/sound/snd_mgr.h b/source/lib/res/sound/snd_mgr.h index 938b5005b7..be6b89e3e4 100644 --- a/source/lib/res/sound/snd_mgr.h +++ b/source/lib/res/sound/snd_mgr.h @@ -26,9 +26,7 @@ #include "../handle.h" -/* - -[KEEP IN SYNC WITH WIKI] +/** overview -------- @@ -84,24 +82,32 @@ terminology "sound instances" store playback parameters (e.g. position), and reference the (centrally cached) "sound data" that will be played. -*/ +**/ // // device enumeration // -// prepare to enumerate all device names (this resets the list returned by -// snd_dev_next). return 0 on success, otherwise -1 (only if the requisite -// OpenAL extension isn't available). on failure, a "cannot enum device" -// message should be presented to the user, and snd_dev_set need not be -// called; OpenAL will use its default device. -// may be called each time the device list is needed. +/** + * prepare to enumerate all device names (this resets the list returned by + * snd_dev_next). + * may be called each time the device list is needed. + * + * @return LibError; fails iff the requisite OpenAL extension isn't available. + * in that case, a "cannot enum device" message should be displayed, but + * snd_dev_set need not be called; OpenAL will use its default device. + **/ extern LibError snd_dev_prepare_enum(); -// return the next device name, or 0 if all have been returned. -// do not call unless snd_dev_prepare_enum succeeded! -// not thread-safe! (static data from snd_dev_prepare_enum is used) +/** + * get next device name in list. + * + * do not call unless snd_dev_prepare_enum succeeded! + * not thread-safe! (static data from snd_dev_prepare_enum is used) + * + * @return device name string, or 0 if all have been returned. + **/ extern const char* snd_dev_next(); @@ -109,32 +115,45 @@ extern const char* snd_dev_next(); // sound system setup // -// tell OpenAL to use the specified device in future. -// name = 0 reverts to OpenAL's default choice, which will also -// be used if this routine is never called. -// -// the device name is typically taken from a config file at init-time; -// the snd_dev* enumeration routines below are used to present a list -// of choices to the user in the options screen. -// -// if OpenAL hasn't yet been initialized (i.e. no sounds have been opened), -// this just stores the device name for use when init does occur. -// note: we can't check now if it's invalid (if so, init will fail). -// otherwise, we shut OpenAL down (thereby stopping all sounds) and -// re-initialize with the new device. that's fairly time-consuming, -// so preferably call this routine before sounds are loaded. -// -// return 0 on success, or the status returned by OpenAL re-init. +/** + * tell OpenAL to use the specified device in future. + * + * @param alc_new_dev_name device name string. if 0, revert to + * OpenAL's default choice, which will also be used if + * this routine is never called. + * the device name is typically taken from a config file at init-time; + * the snd_dev* enumeration routines above are used to present a list + * of choices to the user in the options screen. + * + * if OpenAL hasn't yet been initialized (i.e. no sounds have been opened), + * this just stores the device name for use when init does occur. + * note: we can't check now if it's invalid (if so, init will fail). + * otherwise, we shut OpenAL down (thereby stopping all sounds) and + * re-initialize with the new device. that's fairly time-consuming, + * so preferably call this routine before sounds are loaded. + * + * @return LibError (the status returned by OpenAL re-init) + **/ extern LibError snd_dev_set(const char* alc_new_dev_name); -// set maximum number of voices to play simultaneously, -// to reduce mixing cost on low-end systems. -// return 0 on success, or 1 if limit was ignored -// (e.g. if higher than an implementation-defined limit anyway). +/** + * set maximum number of voices to play simultaneously; + * this can be used to reduce mixing cost on low-end systems. + * + * @param cap maximum number of voices. ignored if higher than + * an implementation-defined limit anyway. + * @return LibError + **/ extern LibError snd_set_max_voices(uint cap); -// set amplitude modifier, which is effectively applied to all sounds. -// must be non-negative; 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence. +/** + * set amplitude modifier, which is effectively applied to all sounds. + * this is akin to a global "volume" control. + * + * @param gain amplitude modifier. must be non-negative; + * 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence. + * @return LibError + **/ extern LibError snd_set_master_gain(float gain); @@ -142,104 +161,148 @@ extern LibError snd_set_master_gain(float gain); // sound instance // -// open and return a handle to a sound instance. -// -// if is a text file (extension "txt"), it is assumed -// to be a definition file containing the sound file name and -// its gain (0.0 .. 1.0). -// otherwise, is taken to be the sound file name and -// gain is set to the default of 1.0 (no attenuation). -// -// is_stream (default false) forces the sound to be opened as a stream: -// opening is faster, it won't be kept in memory, but only one instance -// can be open at a time. +/** + * open and return a handle to a sound instance. + * this loads the sound data and makes it ready for other snd_* APIs. + * + * @param snd_fn input filename. if a text file (extension "txt"), it is + * assumed to be a definition file containing the sound file name and + * its gain (0.0 .. 1.0). + * otherwise, it is taken to be the sound file name and + * gain is set to the default of 1.0 (no attenuation). + * + * @param is_stream (default false) forces the sound to be opened as a + * stream: opening is faster, it won't be kept in memory, but + * only one instance can be open at a time. + * @return Handle or LibError + **/ extern Handle snd_open(const char* snd_fn, bool stream = false); -// close the sound and set hs to 0. if it was playing, -// it will be stopped. sounds are closed automatically when done -// playing; this is provided for completeness only. +/** + * close the sound instance. if it was playing, it will be stopped. + * + * rationale: sounds are already closed automatically when done playing; + * this API is provided for completeness only. + * + * @param hs Handle to sound instance. zeroed afterwards. + * @return LibError + **/ extern LibError snd_free(Handle& hs); -// request the sound be played. once done playing, the sound is -// automatically closed (allows fire-and-forget play code). -// if no hardware voice is available, this sound may not be played at all, -// or in the case of looped sounds, start later. -// priority (min 0 .. max 1, default 0) indicates which sounds are -// considered more important; this is attenuated by distance to the -// listener (see snd_update). +/** + * start playing the sound. + * + * Notes: + *

    + *
  • once done playing, the sound is automatically closed (allows + * fire-and-forget play code). + *
  • if no hardware voice is available, this sound may not be + * played at all, or in the case of looped sounds, start later. + *
+ * + * @param priority (min 0 .. max 1, default 0) indicates which sounds are + * considered more important (i.e. will override others when no hardware + * voices are available). the static priority is attenuated by + * distance to the listener; see snd_update. + * + * @return LibError + **/ extern LibError snd_play(Handle hs, float priority = 0.0f); -// change 3d position of the sound source. -// if relative (default false), (x,y,z) is treated as relative to the -// listener; otherwise, it is the position in world coordinates. -// -// may be called at any time; fails with invalid handle return if -// the sound has already been closed (e.g. it never played). +/** + * change 3d position of the sound source. + * + * may be called at any time; fails with invalid handle return if + * the sound has already been closed (e.g. it never played). + * + * @param relative treat (x,y,z) as relative to the listener; + * if false (the default), it is the position in world coordinates. + * @return LibError + **/ extern LibError snd_set_pos(Handle hs, float x, float y, float z, bool relative = false); -// change gain (amplitude modifier) of the sound source. -// must be non-negative; 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence. -// -// should not be called during a fade (see note in implementation); -// fails with invalid handle return if the sound has already been -// closed (e.g. it never played). +/** + * change gain (amplitude modifier) of the sound source. + * + * should not be called during a fade (see note in implementation); + * fails with invalid handle return if the sound has already been + * closed (e.g. it never played). + * + * @param gain amplitude modifier. must be non-negative; + * 1 -> unattenuated, 0.5 -> -6 dB, 0 -> silence. + * @return LibError + **/ extern LibError snd_set_gain(Handle hs, float gain); -// change pitch shift of the sound source. -// 1.0 means no change; each reduction by 50% equals a pitch shift of -// -12 semitones (one octave). zero is invalid. -// -// may be called at any time; fails with invalid handle return if -// the sound has already been closed (e.g. it never played). +/** + * change pitch shift of the sound source. + * + * may be called at any time; fails with invalid handle return if + * the sound has already been closed (e.g. it never played). + * + * @param pitch 1.0 means no change; each reduction by 50% equals a + * pitch shift of -12 semitones (one octave). zero is invalid. + * @return LibError + **/ extern LibError snd_set_pitch(Handle hs, float pitch); -// enable/disable looping on the sound source. -// used to implement variable-length sounds (e.g. while building). -// -// may be called at any time; fails with invalid handle return if -// the sound has already been closed (e.g. it never played). -// -// notes: -// - looping sounds are not discarded if they cannot be played for lack of -// a hardware voice at the moment play was requested. -// - once looping is again disabled and the sound has reached its end, -// the sound instance is freed automatically (as if never looped). +/** + * enable/disable looping on the sound source. + * used to implement variable-length sounds (e.g. while building). + * + * may be called at any time; fails with invalid handle return if + * the sound has already been closed (e.g. it never played). + * + * Notes: + *
    + *
  • looping sounds are not discarded if they cannot be played for + * lack of a hardware voice at the moment play was requested. + *
  • once looping is again disabled and the sound has reached its end, + * the sound instance is freed automatically (as if never looped). + *
+ * @return LibError + **/ extern LibError snd_set_loop(Handle hs, bool loop); - +/// types of fade in/out operations enum FadeType { - FT_NONE, - FT_LINEAR, - FT_EXPONENTIAL, - FT_S_CURVE, + FT_NONE, /// currently no fade in progres + FT_LINEAR, /// f(t) = t + FT_EXPONENTIAL, /// f(t) = t**3 + FT_S_CURVE, /// cosine curve - FT_ABORT + FT_ABORT /// abort and mark pending fade as complete }; -// fade the sound source in or out over time. -// its gain starts at (immediately) and is moved toward -// over seconds. determines the fade curve: -// linear, exponential or S-curve. for guidance on which to use, see -// http://www.transom.org/tools/editing_mixing/200309.stupidfadetricks.html -// you can also pass FT_ABORT to stop fading (if in progress) and -// set gain to the current parameter. -// special cases: -// - if < 0 (an otherwise illegal value), the sound's -// current gain is used as the start value (useful for fading out). -// - if is 0, the sound is freed when the fade completes or -// is aborted, thus allowing fire-and-forget fadeouts. no cases are -// foreseen where this is undesirable, and it is easier to implement -// than an extra set-free-after-fade-flag function. -// -// may be called at any time; fails with invalid handle return if -// the sound has already been closed (e.g. it never played). -// -// note that this function doesn't busy-wait until the fade is complete; -// any number of fades may be active at a time (allows cross-fading). -// each snd_update calculates a new gain value for all pending fades. -// it is safe to start another fade on the same sound source while -// one is already in progress; the old one will be discarded. +/** + * fade the sound source in or out over time. + * + * may be called at any time; fails with invalid handle return if + * the sound has already been closed (e.g. it never played). + * + * gain starts at (immediately) and is moved toward + * over seconds. + * @param type of fade curve: linear, exponential or S-curve. + * for guidance on which to use, see + * http://www.transom.org/tools/editing_mixing/200309.stupidfadetricks.html + * you can also pass FT_ABORT to stop fading (if in progress) and + * set gain to the current parameter. + * special cases: + * - if < 0 (an otherwise illegal value), the sound's + * current gain is used as the start value (useful for fading out). + * - if is 0, the sound is freed when the fade completes or + * is aborted, thus allowing fire-and-forget fadeouts. no cases are + * foreseen where this is undesirable, and it is easier to implement + * than an extra set-free-after-fade-flag function. + * + * note that this function doesn't busy-wait until the fade is complete; + * any number of fades may be active at a time (allows cross-fading). + * each snd_update calculates a new gain value for all pending fades. + * it is safe to start another fade on the same sound source while + * one is already in progress; the old one will be discarded. + * @return LibError + **/ extern LibError snd_fade(Handle hvs, float initial_gain, float final_gain, float length, FadeType type); @@ -248,27 +311,41 @@ extern LibError snd_fade(Handle hvs, float initial_gain, float final_gain, // sound engine // -// (temporarily) disable all sound output. because it causes future snd_open -// calls to immediately abort before they demand-initialize OpenAL, -// startup is sped up considerably (500..1000ms). therefore, this must be -// called before the first snd_open to have any effect; otherwise, the -// cat will already be out of the bag and we debug_warn of it. -// -// rationale: this is a quick'n dirty way of speeding up startup during -// development without having to change the game's sound code. -// -// can later be called to reactivate sound; all settings ever changed -// will be applied and subsequent sound load / play requests will work. +/** + * (temporarily) disable all sound output. + * + * because it causes future snd_open calls to immediately abort before they + * demand-initialize OpenAL, startup is sped up considerably (500..1000ms). + * therefore, this must be called before the first snd_open to have + * any effect; otherwise, the cat will already be out of the bag and + * we debug_warn of it. + * + * rationale: this is a quick'n dirty way of speeding up startup during + * development without having to change the game's sound code. + * + * can later be called to reactivate sound; all settings ever changed + * will be applied and subsequent sound load / play requests will work. + * @return LibError + **/ extern LibError snd_disable(bool disabled); -// perform housekeeping (e.g. streaming); call once a frame. -// -// additionally, if any parameter is non-NULL, we set the listener -// position, look direction, and up vector (in world coordinates). +/** + * perform housekeeping (e.g. streaming); call once a frame. + * + * all parameters are expressed in world coordinates. they can all be NULL + * to avoid updating the listener data; this is useful when the game world + * has not been initialized yet. + * @param pos listener's position + * @param dir listener view direction + * @param up listener's local up vector + * @return LibError + **/ extern LibError snd_update(const float* pos, const float* dir, const float* up); -// free all resources and shut down the sound system. -// call before h_mgr_shutdown. +/** + * free all resources and shut down the sound system. + * call before h_mgr_shutdown. + **/ extern void snd_shutdown(); #endif // #ifndef SND_MGR_H__ diff --git a/source/lib/self_test.h b/source/lib/self_test.h index 2b415d5c5a..5fa38697a3 100644 --- a/source/lib/self_test.h +++ b/source/lib/self_test.h @@ -172,4 +172,7 @@ extern int self_test_register(SelfTestRecord* r); // set/cleared by run_self_test. extern bool self_test_active; +#define TS_ASSERT_OK(expr) TS_ASSERT_EQUAL((expr), ERR_OK) +#define TS_ASSERT_STR_EQUAL(str1, str2) TS_ASSERT(!strcmp((str1), (str2))) + #endif // #ifndef SELF_TEST_H__ diff --git a/source/lib/string_s.cpp b/source/lib/string_s.cpp index 71b18246ba..1bca3d7778 100644 --- a/source/lib/string_s.cpp +++ b/source/lib/string_s.cpp @@ -203,200 +203,3 @@ int tcat_s(tchar* dst, size_t max_dst_chars, const tchar* src) } #endif // #if !HAVE_STRING_S - - -////////////////////////////////////////////////////////////////////////////// -// -// built-in self test -// -////////////////////////////////////////////////////////////////////////////// - -namespace test { - -#if SELF_TEST_ENABLED - -// note: avoid 4-byte strings - they would trigger WARN_IF_PTR_LEN. - -static const tchar* s0 = T(""); -static const tchar* s1 = T("a"); -static const tchar* s5 = T("abcde"); -static const tchar* s10 = T("abcdefghij"); - -static tchar d1[1]; -static tchar d2[2]; -static tchar d3[3]; -static tchar d5[5]; -static tchar d6[6]; -static tchar d10[10]; -static tchar d11[11]; - -static tchar no_null[] = { 'n','o','_','n','u','l','l'}; - - - -#define TEST_LEN(string, limit, expected) \ - TEST(tnlen((string), (limit)) == (expected)); - -#define TEST_CPY(dst, dst_max, src, expected_ret, expected_dst) \ -STMT( \ - int ret = tcpy_s((dst), dst_max, (src)); \ - TEST(ret == expected_ret); \ - if(dst != 0) \ - TEST(!tcmp(dst, T(expected_dst))); \ -) -#define TEST_CPY2(dst, src, expected_ret, expected_dst) \ -STMT( \ - int ret = tcpy_s((dst), ARRAY_SIZE(dst), (src)); \ - TEST(ret == expected_ret); \ - if(dst != 0) \ - TEST(!tcmp(dst, T(expected_dst))); \ -) -#define TEST_NCPY(dst, src, max_src_chars, expected_ret, expected_dst) \ -STMT( \ - int ret = tncpy_s((dst), ARRAY_SIZE(dst), (src), (max_src_chars)); \ - TEST(ret == expected_ret); \ - if(dst != 0) \ - TEST(!tcmp(dst, T(expected_dst))); \ -) - -#define TEST_CAT(dst, dst_max, src, expected_ret, expected_dst) \ -STMT( \ - int ret = tcat_s((dst), dst_max, (src)); \ - TEST(ret == expected_ret); \ - if(dst != 0) \ - TEST(!tcmp(dst, T(expected_dst))); \ -) -#define TEST_CAT2(dst, dst_val, src, expected_ret, expected_dst) \ -STMT( \ - tcpy(dst, T(dst_val)); \ - int ret = tcat_s((dst), ARRAY_SIZE(dst), (src)); \ - TEST(ret == expected_ret); \ - if(dst != 0) \ - TEST(!tcmp(dst, T(expected_dst))); \ -) -#define TEST_NCAT(dst, dst_val, src, max_src_chars, expected_ret, expected_dst)\ -STMT( \ - tcpy(dst, T(dst_val)); \ - int ret = tncat_s((dst), ARRAY_SIZE(dst), (src), (max_src_chars)); \ - TEST(ret == expected_ret); \ - if(dst != 0) \ - TEST(!tcmp(dst, T(expected_dst))); \ -) - - -// contains all tests that verify correct behavior for bogus input. -// our implementation suppresses error dialogs while the self-test is active, -// but others (e.g. the functions shipped with VC8) do not. -// since we have no control over their error reporting (which ends up taking -// down the program), we must skip this part of the test if using them. -// this is still preferable to completely disabling the self-test. -static void test_param_validation() -{ -#if !HAVE_STRING_S - TEST_CPY(0 ,0,0 , EINVAL,""); // all invalid - TEST_CPY(0 ,0,s1, EINVAL,""); // dst = 0, max = 0 - TEST_CPY(0 ,1,s1, EINVAL,""); // dst = 0, max > 0 - TEST_CPY(d1,1,0 , EINVAL,""); // src = 0 - TEST_CPY(d1,0,s1, ERANGE,""); // max_dst_chars = 0 - - TEST_CPY2(d1 ,s1, ERANGE,""); - TEST_CPY2(d1 ,s5, ERANGE,""); - TEST_CPY2(d5 ,s5, ERANGE,""); - - TEST_NCPY(d1 ,s1,1, ERANGE,""); - TEST_NCPY(d1 ,s5,1, ERANGE,""); - TEST_NCPY(d5 ,s5,5, ERANGE,""); - - TEST_CAT(0 ,0,0 , EINVAL,""); // all invalid - TEST_CAT(0 ,0,s1, EINVAL,""); // dst = 0, max = 0 - TEST_CAT(0 ,1,s1, EINVAL,""); // dst = 0, max > 0 - TEST_CAT(d1,1,0 , EINVAL,""); // src = 0 - TEST_CAT(d1,0,s1, ERANGE,""); // max_dst_chars = 0 - TEST_CAT(no_null,5,s1, ERANGE,""); // dst not terminated - - TEST_CAT2(d1 ,"" ,s1, ERANGE,""); - TEST_CAT2(d1 ,"" ,s5, ERANGE,""); - TEST_CAT2(d10,"" ,s10, ERANGE,""); // empty, total overflow - TEST_CAT2(d10,"12345",s5 , ERANGE,""); // not empty, overflow - TEST_CAT2(d10,"12345",s10, ERANGE,""); // not empty, total overflow - - TEST_NCAT(d1 ,"" ,s1,1, ERANGE,""); - TEST_NCAT(d1 ,"" ,s5,5, ERANGE,""); - TEST_NCAT(d10,"" ,s10,10, ERANGE,""); // empty, total overflow - TEST_NCAT(d10,"12345",s5 ,5 , ERANGE,""); // not empty, overflow - TEST_NCAT(d10,"12345",s10,10, ERANGE,""); // not empty, total overflow -#endif -} - - -static void test_length() -{ - TEST_LEN(s0, 0 , 0 ); - TEST_LEN(s0, 1 , 0 ); - TEST_LEN(s0, 50, 0 ); - TEST_LEN(s1, 0 , 0 ); - TEST_LEN(s1, 1 , 1 ); - TEST_LEN(s1, 50, 1 ); - TEST_LEN(s5, 0 , 0 ); - TEST_LEN(s5, 1 , 1 ); - TEST_LEN(s5, 50, 5 ); - TEST_LEN(s10,9 , 9 ); - TEST_LEN(s10,10, 10); - TEST_LEN(s10,11, 10); -} - - -static void test_copy() -{ - TEST_CPY2(d2 ,s1, 0,"a"); - TEST_CPY2(d6 ,s5, 0,"abcde"); - TEST_CPY2(d11,s5, 0,"abcde"); - - TEST_NCPY(d2 ,s1,1, 0,"a"); - TEST_NCPY(d6 ,s5,5, 0,"abcde"); - TEST_NCPY(d11,s5,5, 0,"abcde"); - - tcpy(d5, T("----")); - TEST_NCPY(d5,s5,0 , 0,""); // specified behavior! see 3.6.2.1.1 #4 - TEST_NCPY(d5,s5,1 , 0,"a"); - TEST_NCPY(d5,s5,4 , 0,"abcd"); - TEST_NCPY(d6,s5,5 , 0,"abcde"); - TEST_NCPY(d6,s5,10, 0,"abcde"); -} - - -static void test_concatenate() -{ - TEST_CAT2(d3 ,"1",s1, 0,"1a"); - TEST_CAT2(d5 ,"1",s1, 0,"1a"); - TEST_CAT2(d6 ,"" ,s5, 0,"abcde"); - TEST_CAT2(d10,"" ,s5, 0,"abcde"); - TEST_CAT2(d10,"1234" ,s5 , 0,"1234abcde"); - - TEST_NCAT(d3 ,"1",s1,1, 0,"1a"); - TEST_NCAT(d5 ,"1",s1,1, 0,"1a"); - TEST_NCAT(d6 ,"" ,s5,5, 0,"abcde"); - TEST_NCAT(d10,"" ,s5,5, 0,"abcde"); - TEST_NCAT(d10,"1234" ,s5 ,5 , 0,"1234abcde"); - - TEST_NCAT(d5,"----",s5,0 , 0,"----"); - TEST_NCAT(d5,"",s5,1 , 0,"a"); - TEST_NCAT(d5,"",s5,4 , 0,"abcd"); - TEST_NCAT(d5,"12",s5,2 , 0,"12ab"); - TEST_NCAT(d6,"",s5,10, 0,"abcde"); -} - - -static void self_test() -{ - test_param_validation(); - test_length(); - test_copy(); - test_concatenate(); -} - -SELF_TEST_RUN; - -#endif // #if SELF_TEST_ENABLED - -} // namespace test diff --git a/source/lib/sysdep/ia32.cpp b/source/lib/sysdep/ia32.cpp index 1b47935a5f..d21d1ab9ff 100644 --- a/source/lib/sysdep/ia32.cpp +++ b/source/lib/sysdep/ia32.cpp @@ -42,9 +42,6 @@ #error ia32.cpp needs inline assembly support! #endif -#define SELF_TEST_ENABLED 1 -#include "self_test.h" - // set by ia32_init, referenced by ia32_memcpy (asm) extern "C" u32 ia32_memcpy_size_mask = 0; @@ -58,7 +55,7 @@ void ia32_init() // .. check for PREFETCHNTA and MOVNTQ support. these are part of the SSE // instruction set, but also supported on older Athlons as part of // the extended AMD MMX set. - if(ia32_cap(SSE) || ia32_cap(AMD_MMX_EXT)) + if(ia32_cap(IA32_CAP_SSE) || ia32_cap(IA32_CAP_AMD_MMX_EXT)) ia32_memcpy_size_mask = ~0u; } @@ -183,7 +180,7 @@ __asm{ // calling conventions. // MSC, ICC and GCC currently return 64 bits in edx:eax, which even // matches rdtsc output, but we play it safe and return a temporary. -u64 rdtsc() +u64 ia32_rdtsc() { u64 c; #if HAVE_MS_ASM @@ -226,7 +223,7 @@ void ia32_debug_break() void mfence() { // Pentium IV - if(ia32_cap(SSE2)) + if(ia32_cap(IA32_CAP_SSE2)) #if HAVE_MS_ASM __asm mfence #elif HAVE_GNU_ASM @@ -248,7 +245,7 @@ void serialize() // CPU / feature detect //----------------------------------------------------------------------------- -bool ia32_cap(CpuCap cap) +bool ia32_cap(IA32Cap cap) { // treated as 128 bit field; order: std ecx, std edx, ext ecx, ext edx // keep in sync with enum CpuCap! @@ -364,7 +361,7 @@ static void get_cpu_type() SAFE_STRCPY(cpu_type, "AMD Athlon"); else { - if(ia32_cap(AMD_MP)) + if(ia32_cap(IA32_CAP_AMD_MP)) SAFE_STRCPY(cpu_type, "AMD Athlon MP"); else SAFE_STRCPY(cpu_type, "AMD Athlon XP"); @@ -465,7 +462,7 @@ static void get_cpu_count() // note: we don't check if it's Intel and P4 or above - HT may be // supported on other CPUs in future. all processors should set this // feature bit correctly, so it's not a problem. - if(ia32_cap(HT)) + if(ia32_cap(IA32_CAP_HT)) { log_id_bits = log2(log_cpu_per_package); // see above last_phys_id = last_log_id = INVALID_ID; @@ -495,7 +492,7 @@ static void check_for_speedstep() { if(vendor == INTEL) { - if(ia32_cap(EST)) + if(ia32_cap(IA32_CAP_EST)) cpu_speedstep = 1; } else if(vendor == AMD) @@ -520,11 +517,11 @@ static void measure_cpu_freq() // make sure the TSC is available, because we're going to // measure actual CPU clocks per known time interval. // counting loop iterations ("bogomips") is unreliable. - if(ia32_cap(TSC)) + if(ia32_cap(IA32_CAP_TSC)) { // note: no need to "warm up" cpuid - it will already have been // called several times by the time this code is reached. - // (background: it's used in rdtsc() to serialize instruction flow; + // (background: it's used in ia32_rdtsc() to serialize instruction flow; // the first call is documented to be slower on Intel CPUs) int num_samples = 16; @@ -550,27 +547,27 @@ static void measure_cpu_freq() do { // note: get_time effectively has a long delay (up to 5 us) - // before returning the time. we call it before rdtsc to + // before returning the time. we call it before ia32_rdtsc to // minimize the delay between actually sampling time / TSC, // thus decreasing the chance for interference. // (if unavoidable background activity, e.g. interrupts, // delays the second reading, inaccuracy is introduced). t1 = get_time(); - c1 = rdtsc(); + c1 = ia32_rdtsc(); } while(t1 == t0); // .. wait until start of next tick and at least 1 ms elapsed. do { const double t2 = get_time(); - const u64 c2 = rdtsc(); + const u64 c2 = ia32_rdtsc(); dc = (i64)(c2 - c1); dt = t2 - t1; } while(dt < 1e-3); // .. freq = (delta_clocks) / (delta_seconds); - // cpuid/rdtsc/timer overhead is negligible. + // ia32_rdtsc/timer overhead is negligible. const double freq = dc / dt; samples[i] = freq; } @@ -676,39 +673,3 @@ LibError ia32_get_call_target(void* ret_addr, void** target) WARN_RETURN(ERR_CPU_UNKNOWN_OPCODE); } - - -//---------------------------------------------------------------------------- -// built-in self test -//---------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - - static void test_float_int() - { - TEST(i32_from_float(0.99999f) == 0); - TEST(i32_from_float(1.0f) == 1); - TEST(i32_from_float(1.01f) == 1); - TEST(i32_from_float(5.6f) == 5); - - TEST(i32_from_double(0.99999) == 0); - TEST(i32_from_double(1.0) == 1); - TEST(i32_from_double(1.01) == 1); - TEST(i32_from_double(5.6) == 5); - - TEST(i64_from_double(0.99999) == 0LL); - TEST(i64_from_double(1.0) == 1LL); - TEST(i64_from_double(1.01) == 1LL); - TEST(i64_from_double(5.6) == 5LL); - } - - static void self_test() - { - test_float_int(); - } - - SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/lib/sysdep/ia32.h b/source/lib/sysdep/ia32.h index cb118ad466..59fac412c4 100644 --- a/source/lib/sysdep/ia32.h +++ b/source/lib/sysdep/ia32.h @@ -89,37 +89,39 @@ extern void* ia32_memcpy(void* dst, const void* src, size_t nbytes); // asm extern uint ia32_control87(uint new_val, uint mask); // asm -extern u64 rdtsc(void); +extern u64 ia32_rdtsc(void); extern void ia32_debug_break(void); -// CPU caps (128 bits) +// CPU capability flags (128 bits) // do not change the order! -enum CpuCap +enum IA32Cap { // standard (ecx) - currently only defined by Intel - SSE3 = 0+0, // Streaming SIMD Extensions 3 - EST = 0+7, // Enhanced Speedstep Technology + IA32_CAP_SSE3 = 0+0, // Streaming SIMD Extensions 3 + IA32_CAP_EST = 0+7, // Enhanced Speedstep Technology // standard (edx) - TSC = 32+4, // TimeStamp Counter - CMOV = 32+15, // Conditional MOVe - MMX = 32+23, // MultiMedia eXtensions - SSE = 32+25, // Streaming SIMD Extensions - SSE2 = 32+26, // Streaming SIMD Extensions 2 - HT = 32+28, // HyperThreading + IA32_CAP_FPU = 32+0, // Floating Point Unit + IA32_CAP_TSC = 32+4, // TimeStamp Counter + IA32_CAP_CMOV = 32+15, // Conditional MOVe + IA32_CAP_MMX = 32+23, // MultiMedia eXtensions + IA32_CAP_SSE = 32+25, // Streaming SIMD Extensions + IA32_CAP_SSE2 = 32+26, // Streaming SIMD Extensions 2 + IA32_CAP_HT = 32+28, // HyperThreading // extended (ecx) // extended (edx) - currently only defined by AMD - AMD_MP = 96+19, // MultiProcessing capable; reserved on AMD64 - AMD_MMX_EXT = 96+22, - AMD_3DNOW_PRO = 96+30, - AMD_3DNOW = 96+31 + IA32_CAP_AMD_MP = 96+19, // MultiProcessing capable; reserved on AMD64 + IA32_CAP_AMD_MMX_EXT = 96+22, + IA32_CAP_AMD_3DNOW_PRO = 96+30, + IA32_CAP_AMD_3DNOW = 96+31 }; -extern bool ia32_cap(CpuCap cap); +// indicate if the CPU supports the indicated cap. +extern bool ia32_cap(IA32Cap cap); extern void ia32_get_cpu_info(void); diff --git a/source/lib/sysdep/win/wdbg_sym.cpp b/source/lib/sysdep/win/wdbg_sym.cpp index d80c2addca..3ed4b6b6a8 100644 --- a/source/lib/sysdep/win/wdbg_sym.cpp +++ b/source/lib/sysdep/win/wdbg_sym.cpp @@ -41,11 +41,6 @@ # include "lib/sysdep/ia32.h" #endif -// raises an an annoying exception, so disable unless needed -#undef SELF_TEST_ENABLED -#define SELF_TEST_ENABLED 0 - - #if MSC_VERSION #pragma comment(lib, "dbghelp.lib") #pragma comment(lib, "oleaut32.lib") // VariantChangeType @@ -1954,241 +1949,3 @@ static LibError wdbg_sym_shutdown() ptr_reset_visited(); return sym_shutdown(); } - - -//---------------------------------------------------------------------------- -// built-in self test -//---------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { -#pragma optimize("", off) - - - -static void test_array() -{ - struct Small - { - int i1; - int i2; - }; - - struct Large - { - double d1; - double d2; - double d3; - double d4; - }; - - Large large_array_of_large_structs[8] = { { 0.0,0.0,0.0,0.0 } }; UNUSED2(large_array_of_large_structs); - Large small_array_of_large_structs[2] = { { 0.0,0.0,0.0,0.0 } }; UNUSED2(small_array_of_large_structs); - Small large_array_of_small_structs[8] = { { 1,2 } }; UNUSED2(large_array_of_small_structs); - Small small_array_of_small_structs[2] = { { 1,2 } }; UNUSED2(small_array_of_small_structs); - - int ints[] = { 1,2,3,4,5 }; UNUSED2(ints); - wchar_t chars[] = { 'w','c','h','a','r','s',0 }; UNUSED2(chars); - - // note: prefer simple error (which also generates stack trace) to - // exception, because it is guaranteed to work (no issues with the - // debugger swallowing exceptions). - DISPLAY_ERROR(L"wdbg_sym self test: check if stack trace below is ok."); - //RaiseException(0xf001,0,0,0); -} - -// also used by test_stl as an element type -struct Nested -{ - int nested_member; - struct Nested* self_ptr; -}; - -static void test_udt() -{ - Nested nested = { 123 }; nested.self_ptr = &nested; - - typedef struct - { - u8 s1; - u8 s2; - char s3; - } - Small; - Small small__ = { 0x55, 0xaa, -1 }; UNUSED2(small__); - - struct Large - { - u8 large_member_u8; - std::string large_member_string; - double large_member_double; - } - large = { 0xff, "large struct string", 123456.0 }; UNUSED2(large); - - - class Base - { - int base_int; - std::wstring base_wstring; - public: - Base() - : base_int(123), base_wstring(L"base wstring") - { - } - }; - class Derived : private Base - { - double derived_double; - public: - Derived() - : derived_double(-1.0) - { - } - } - derived; - - test_array(); -} - -// STL containers and their contents -static void test_stl() -{ - std::vector v_wstring; - v_wstring.push_back(L"ws1"); v_wstring.push_back(L"ws2"); - - std::deque d_int; - d_int.push_back(1); d_int.push_back(2); d_int.push_back(3); - std::deque d_string; - d_string.push_back("a"); d_string.push_back("b"); d_string.push_back("c"); - - std::list l_float; - l_float.push_back(0.1f); l_float.push_back(0.2f); l_float.push_back(0.3f); l_float.push_back(0.4f); - - std::map m_string_int; - m_string_int.insert(std::make_pair("s5", 5)); - m_string_int.insert(std::make_pair("s6", 6)); - m_string_int.insert(std::make_pair("s7", 7)); - std::map m_int_string; - m_int_string.insert(std::make_pair(1, "s1")); - m_int_string.insert(std::make_pair(2, "s2")); - m_int_string.insert(std::make_pair(3, "s3")); - std::map m_int_int; - m_int_int.insert(std::make_pair(1, 1)); - m_int_int.insert(std::make_pair(2, 2)); - m_int_int.insert(std::make_pair(3, 3)); - - STL_HASH_MAP hm_string_int; - hm_string_int.insert(std::make_pair("s5", 5)); - hm_string_int.insert(std::make_pair("s6", 6)); - hm_string_int.insert(std::make_pair("s7", 7)); - STL_HASH_MAP hm_int_string; - hm_int_string.insert(std::make_pair(1, "s1")); - hm_int_string.insert(std::make_pair(2, "s2")); - hm_int_string.insert(std::make_pair(3, "s3")); - STL_HASH_MAP hm_int_int; - hm_int_int.insert(std::make_pair(1, 1)); - hm_int_int.insert(std::make_pair(2, 2)); - hm_int_int.insert(std::make_pair(3, 3)); - - - std::set s_uintptr; - s_uintptr.insert(0x123); s_uintptr.insert(0x456); - - // empty - std::deque d_u8_empty; - std::list l_nested_empty; - std::map m_double_empty; - std::multimap mm_int_empty; - std::set s_uint_empty; - std::multiset ms_char_empty; - std::vector v_double_empty; - std::queue q_double_empty; - std::stack st_double_empty; -#if HAVE_STL_HASH - STL_HASH_MAP hm_double_empty; - STL_HASH_MULTIMAP hmm_double_empty; - STL_HASH_SET hs_double_empty; - STL_HASH_MULTISET hms_double_empty; -#endif -#if HAVE_STL_SLIST - STL_SLIST sl_double_empty; -#endif - std::string str_empty; - std::wstring wstr_empty; - - test_udt(); - - // uninitialized - std::deque d_u8_uninit; - std::list l_nested_uninit; - std::map m_double_uninit; - std::multimap mm_int_uninit; - std::set s_uint_uninit; - std::multiset ms_char_uninit; - std::vector v_double_uninit; - std::queue q_double_uninit; - std::stack st_double_uninit; -#if HAVE_STL_HASH - STL_HASH_MAP hm_double_uninit; - STL_HASH_MULTIMAP hmm_double_uninit; - STL_HASH_SET hs_double_uninit; - STL_HASH_MULTISET hms_double_uninit; -#endif -#if HAVE_STL_SLIST - STL_SLIST sl_double_uninit; -#endif - std::string str_uninit; - std::wstring wstr_uninit; -} - - -// also exercises all basic types because we need to display some values -// anyway (to see at a glance whether symbol engine addrs are correct) -static void test_addrs(int p_int, double p_double, char* p_pchar, uintptr_t p_uintptr) -{ - debug_printf("\nTEST_ADDRS\n"); - - uint l_uint = 0x1234; - bool l_bool = true; UNUSED2(l_bool); - wchar_t l_wchars[] = L"wchar string"; - enum TestEnum { VAL1=1, VAL2=2 } l_enum = VAL1; - u8 l_u8s[] = { 1,2,3,4 }; - void (*l_funcptr)(void) = test_stl; - - static double s_double = -2.718; - static char s_chars[] = {'c','h','a','r','s',0}; - static void (*s_funcptr)(int, double, char*, uintptr_t) = test_addrs; - static void* s_ptr = (void*)(uintptr_t)0x87654321; - static HDC s_hdc = (HDC)0xff0; - - debug_printf("p_int addr=%p val=%d\n", &p_int, p_int); - debug_printf("p_double addr=%p val=%g\n", &p_double, p_double); - debug_printf("p_pchar addr=%p val=%s\n", &p_pchar, p_pchar); - debug_printf("p_uintptr addr=%p val=%lu\n", &p_uintptr, p_uintptr); - - debug_printf("l_uint addr=%p val=%u\n", &l_uint, l_uint); - debug_printf("l_wchars addr=%p val=%ws\n", &l_wchars, l_wchars); - debug_printf("l_enum addr=%p val=%d\n", &l_enum, l_enum); - debug_printf("l_u8s addr=%p val=%d\n", &l_u8s, l_u8s); - debug_printf("l_funcptr addr=%p val=%p\n", &l_funcptr, l_funcptr); - - test_stl(); - - int uninit_int; UNUSED2(uninit_int); - float uninit_float; UNUSED2(uninit_float); - double uninit_double; UNUSED2(uninit_double); - bool uninit_bool; UNUSED2(uninit_bool); - HWND uninit_hwnd; UNUSED2(uninit_hwnd); -} - - -static void self_test() -{ - test_addrs(123, 3.1415926535897932384626, "pchar string", 0xf00d); -} - -SELF_TEST_RUN; - -#pragma optimize("", on) -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/lib/sysdep/win/wtime.cpp b/source/lib/sysdep/win/wtime.cpp index 86ac46e118..447c2b54dd 100644 --- a/source/lib/sysdep/win/wtime.cpp +++ b/source/lib/sysdep/win/wtime.cpp @@ -190,7 +190,7 @@ static LibError choose_impl() // will do this as well (if not to save power, for heat reasons). // frequency changes are too often and drastic to correct, // and we don't want to mess with the system power settings => unsafe. - if(cpu_freq > 0.0 && ia32_cap(TSC)) + if(cpu_freq > 0.0 && ia32_cap(IA32_CAP_TSC)) { safe = (cpus == 1 && cpu_speedstep == 0); SAFETY_OVERRIDE(HRT_TSC); @@ -299,7 +299,7 @@ static i64 ticks_lk() // TSC #if CPU_IA32 && !defined(NO_TSC) case HRT_TSC: - return (i64)rdtsc(); + return (i64)ia32_rdtsc(); #endif // QPC diff --git a/source/lib/timer.h b/source/lib/timer.h index 10646a8714..da789e4892 100644 --- a/source/lib/timer.h +++ b/source/lib/timer.h @@ -51,7 +51,7 @@ extern void calc_fps(void); // since TIMER_ACCRUE et al. are called so often, we try to keep // overhead to an absolute minimum. this flag allows storing -// raw tick counts (e.g. CPU cycles returned by rdtsc) instead of +// raw tick counts (e.g. CPU cycles returned by ia32_rdtsc) instead of // absolute time. there are two benefits: // - no need to convert from raw->time on every call // (instead, it's only done once when displaying the totals) @@ -213,7 +213,7 @@ public: { #if TIMER_USE_RAW_TICKS # if CPU_IA32 - t0 = rdtsc(); + t0 = ia32_rdtsc(); # else # error "port" # endif @@ -226,7 +226,7 @@ public: { #if TIMER_USE_RAW_TICKS # if CPU_IA32 - TimerUnit t1 = rdtsc(); + TimerUnit t1 = ia32_rdtsc(); # else # error "port" # endif diff --git a/source/maths/Matrix3D.cpp b/source/maths/Matrix3D.cpp index 6c5d0f16ab..f3374e8a3c 100644 --- a/source/maths/Matrix3D.cpp +++ b/source/maths/Matrix3D.cpp @@ -524,74 +524,3 @@ void CMatrix3D::SetRotation(const CQuaternion& quat) { quat.ToMatrix(*this); } - - -//---------------------------------------------------------------------------- -// built-in self test -//---------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - -static void test_inverse() -{ - CMatrix3D m; - srand(0); - for (int i = 0; i < 4; ++i) - { - for (int j = 0; j < 16; ++j) - m._data[j] = -1.0f + 2.0f*(rand()/(float)RAND_MAX); - CMatrix3D n; - m.GetInverse(n); - m *= n; - for (int x = 0; x < 4; ++x) - for (int y = 0; y < 4; ++y) - { - float expected = (x==y)? 1.0f : 0.0f; // identity should have 1s on diagonal - TEST(feq(m(x,y), expected)); - } - } -} - -static void test_quats() -{ - srand(0); - for (int i = 0; i < 4; ++i) - { - CQuaternion q; - q.FromEulerAngles( - -6.28f + 12.56f*(rand()/(float)RAND_MAX), - -6.28f + 12.56f*(rand()/(float)RAND_MAX), - -6.28f + 12.56f*(rand()/(float)RAND_MAX) - ); - CMatrix3D m; - q.ToMatrix(m); - CQuaternion q2 = m.GetRotation(); - - // I hope there's a good reason why they're sometimes negated, and - // it's not just a bug... - bool ok_oneway = - feq(q2.m_W, q.m_W) && - feq(q2.m_V.X, q.m_V.X) && - feq(q2.m_V.Y, q.m_V.Y) && - feq(q2.m_V.Z, q.m_V.Z); - bool ok_otherway = - feq(q2.m_W, -q.m_W) && - feq(q2.m_V.X, -q.m_V.X) && - feq(q2.m_V.Y, -q.m_V.Y) && - feq(q2.m_V.Z, -q.m_V.Z); - TEST(ok_oneway ^ ok_otherway); - } -} - - -static void self_test() -{ - test_inverse(); - test_quats(); -} - -SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/ps/CStr.cpp b/source/ps/CStr.cpp index 346f066366..2c22f90de6 100644 --- a/source/ps/CStr.cpp +++ b/source/ps/CStr.cpp @@ -128,36 +128,6 @@ CStrW CStr8::FromUTF8() const } -//---------------------------------------------------------------------------- -// built-in self test -//---------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - -static void test1() -{ - const wchar_t chr_utf16[] = { 0x12, 0xff, 0x1234, 0x3456, 0x5678, 0x7890, 0x9abc, 0xbcde, 0xfffe }; - const unsigned char chr_utf8[] = { 0x12, 0xc3, 0xbf, 0xe1, 0x88, 0xb4, 0xe3, 0x91, 0x96, 0xe5, 0x99, 0xb8, 0xe7, 0xa2, 0x90, 0xe9, 0xaa, 0xbc, 0xeb, 0xb3, 0x9e, 0xef, 0xbf, 0xbe }; - CStrW str_utf16 (chr_utf16, sizeof(chr_utf16)/sizeof(wchar_t)); - CStr8 str_utf8 = str_utf16.ToUTF8(); - TEST(str_utf8.length() == sizeof(chr_utf8)); - TEST(memcmp(str_utf8.data(), chr_utf8, sizeof(chr_utf8)) == 0); - TEST(str_utf8.FromUTF8() == str_utf16); -} - - -static void self_test() -{ - test1(); -} - -SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED - - #else // The following code is compiled twice, as CStrW then as CStr8: diff --git a/source/ps/Parser.cpp b/source/ps/Parser.cpp index 1bf15722df..618d6271af 100644 --- a/source/ps/Parser.cpp +++ b/source/ps/Parser.cpp @@ -1089,115 +1089,3 @@ CParser& CParserCache::Get(const char* str) return *parser; } } - - - - - -//---------------------------------------------------------------------------- -// built-in self test -//---------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - -static void test1() -{ - CParser Parser; - Parser.InputTaskType("test", "_$ident_=_$value_"); - - std::string str; - int i; - - CParserLine Line; - - TEST(Line.ParseString(Parser, "value=23")); - - TEST(Line.GetArgString(0, str) && str == "value"); - TEST(Line.GetArgInt(1, i) && i == 23); -} - - -static void test2() -{ - CParser Parser; - Parser.InputTaskType("test", "_$value_[$value]_"); - - std::string str; - - CParserLine Line; - - TEST(Line.ParseString(Parser, "12 34")); - TEST(Line.GetArgCount() == 2); - TEST(Line.GetArgString(0, str) && str == "12"); - TEST(Line.GetArgString(1, str) && str == "34"); - - TEST(Line.ParseString(Parser, "56")); - TEST(Line.GetArgCount() == 1); - TEST(Line.GetArgString(0, str) && str == "56"); - - TEST(! Line.ParseString(Parser, " ")); -} - - -static void test3() -{ - CParser Parser; - Parser.InputTaskType("test", "_[$value]_[$value]_[$value]_"); - - std::string str; - - CParserLine Line; - - TEST(Line.ParseString(Parser, "12 34 56")); - TEST(Line.GetArgCount() == 3); - TEST(Line.GetArgString(0, str) && str == "12"); - TEST(Line.GetArgString(1, str) && str == "34"); - TEST(Line.GetArgString(2, str) && str == "56"); - - TEST(Line.ParseString(Parser, "78 90")); - TEST(Line.GetArgCount() == 2); - TEST(Line.GetArgString(0, str) && str == "78"); - TEST(Line.GetArgString(1, str) && str == "90"); - - TEST(Line.ParseString(Parser, "ab")); - TEST(Line.GetArgCount() == 1); - TEST(Line.GetArgString(0, str) && str == "ab"); - - TEST(Line.ParseString(Parser, " ")); - TEST(Line.GetArgCount() == 0); -} - - -static void test4() -{ - CParser Parser; - Parser.InputTaskType("test", "<[_a_][_b_]_x_>"); - - std::string str; - - CParserLine Line; - TEST(Line.ParseString(Parser, "a b x a b x")); - TEST(Line.ParseString(Parser, "a x b x")); - TEST(Line.ParseString(Parser, "a x")); - TEST(Line.ParseString(Parser, "b x")); - TEST(Line.ParseString(Parser, "x")); - TEST(! Line.ParseString(Parser, "a x c x")); - TEST(! Line.ParseString(Parser, "a b a x")); - TEST(! Line.ParseString(Parser, "a")); - TEST(! Line.ParseString(Parser, "a a x")); - TEST(Line.ParseString(Parser, "a x a b x a x b x b x b x b x a x a x a b x a b x b x a x")); -} - -static void self_test() -{ - test1(); - test2(); - test3(); - test4(); -} - -SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED diff --git a/source/ps/XML/XMLWriter.cpp b/source/ps/XML/XMLWriter.cpp index 06f9c37b1f..3f36196e95 100644 --- a/source/ps/XML/XMLWriter.cpp +++ b/source/ps/XML/XMLWriter.cpp @@ -182,70 +182,3 @@ template <> void XMLWriter_File::ElementAttribute(const char* name, const { ElementAttribute(name, value.ToUTF8(), newelement); } - - -//---------------------------------------------------------------------------- -// built-in self test -//---------------------------------------------------------------------------- - -#if SELF_TEST_ENABLED -namespace test { - -static void test1() -{ - XML_Start("utf-8"); - XML_Doctype("Scenario", "/maps/scenario.dtd"); - - { - XML_Element("Scenario"); - { - XML_Comment("Comment test."); - XML_Comment("Comment test again."); - { - XML_Element("a"); - XML_Attribute("one", 1); - XML_Attribute("two", "TWO"); - XML_Text("b"); - XML_Text(" (etc)"); - } - { - XML_Element("c"); - XML_Text("d"); - } - XML_Setting("c2", "d2"); - { - XML_Element("e"); - { - { - XML_Element("f"); - XML_Text("g"); - } - { - XML_Element("h"); - } - { - XML_Element("i"); - XML_Attribute("j", 1.23); - { - XML_Element("k"); - XML_Attribute("l", 2.34); - XML_Text("m"); - } - } - } - } - } - } - - // For this test to be useful, it should actually test something. -} - -static void self_test() -{ - test1(); -} - -SELF_TEST_RUN; - -} // namespace test -#endif // #if SELF_TEST_ENABLED