From 7cb82ada2fe4cdd121c311b21a44ed96a943fb73 Mon Sep 17 00:00:00 2001 From: janwas Date: Fri, 23 Jun 2006 17:41:55 +0000 Subject: [PATCH] # lots of housekeeping/fixes premake: document extra_params ogl: remove EXT_ symbols, use glext instead adts: move cache into separate file (also remove old cache impl) move SDL files in sysdep/win to libraries/SDL ScopeTimerAccrue: change #ifdef spaghetti into templated policy class app_hooks, define VOID_FUNC to FUNC(.., (void) ) look at stalk walk code; any reason not to work on Win64? replace ERR_TEX_CODEC_CANNOT_HANDLE with INFO_* - not an error. also ERR_SYM_SUPPRESS_OUTPUT ERR_SYM_SINGLE_SYMBOL_LIMIT wdbg_sym: only import RtlCaptureContext once (not every walk_stack) add sys_last_error (!GetLastError on win32); converts to text in display_error, also show sys_last_error and errno app_hooks: move i18n impl out (belongs in pyrogenesis) fixes to string_s test. refs #124 This was SVN commit r4020. --- build/premake/premake.lua | 38 +- source/lib/adts.cpp | 25 - source/lib/adts.h | 862 +---------------------------- source/lib/app_hooks.cpp | 25 +- source/lib/app_hooks.h | 12 +- source/lib/cache_adt.h | 731 ++++++++++++++++++++++++ source/lib/debug.cpp | 18 +- source/lib/lib_errors.h | 12 +- source/lib/ogl.h | 55 -- source/lib/res/file/file_cache.cpp | 2 +- source/lib/sysdep/sysdep.h | 9 +- source/lib/sysdep/win/SDL_keysym.h | 316 ----------- source/lib/sysdep/win/SDL_vkeys.h | 81 --- source/lib/sysdep/win/wdbg_sym.cpp | 41 +- source/lib/sysdep/win/wsdl.cpp | 5 +- source/lib/sysdep/win/wsdl.h | 2 +- source/lib/sysdep/win/wsysdep.cpp | 13 +- source/lib/tests/test_adts.h | 105 ---- source/lib/tests/test_cache_adt.h | 108 ++++ source/lib/tests/test_string_s.h | 9 +- source/lib/timer.h | 84 +-- source/ps/GameSetup/GameSetup.cpp | 6 + source/ps/Pyrogenesis.cpp | 27 + source/ps/Pyrogenesis.h | 4 + 24 files changed, 1038 insertions(+), 1552 deletions(-) delete mode 100644 source/lib/adts.cpp create mode 100644 source/lib/cache_adt.h delete mode 100644 source/lib/sysdep/win/SDL_keysym.h delete mode 100644 source/lib/sysdep/win/SDL_vkeys.h create mode 100644 source/lib/tests/test_cache_adt.h diff --git a/build/premake/premake.lua b/build/premake/premake.lua index 49c5e83a6b..6635ea4bcc 100755 --- a/build/premake/premake.lua +++ b/build/premake/premake.lua @@ -109,6 +109,11 @@ function package_create(package_name, target_type) end +-- extra_params: table including zero or more of the following: +-- * no_default_pch: (any type) prevents adding the PCH include dir. +-- see setup_static_lib_package() for explanation of this scheme and rationale. +-- * extra_files: table of filenames (relative to source_root) to add to project +-- * extra_links: table of library names to add to link step function package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) -- We don't want the VC project to be deeply nested (once for each @@ -161,10 +166,12 @@ end -- more to the point, it is necessary to efficiently support a separate -- test executable that also includes much of the game code. --- names of all static libs created. added to the main app project later --- (see explanation at end of this file) +-- names of all static libs created. automatically added to the +-- main app project later (see explanation at end of this file) static_lib_names = {} +-- set up one of the static libraries into which the main engine code is split. +-- extra_params: see package_add_contents(). -- note: rel_source_dirs and rel_include_dirs are relative to global source_root. local function setup_static_lib_package (package_name, rel_source_dirs, extern_libs, extra_params) @@ -223,6 +230,7 @@ function setup_all_libs () } extern_libs = { "spidermonkey", + "sdl", -- key definitions "xerces", "opengl", "zlib", @@ -238,6 +246,7 @@ function setup_all_libs () } extern_libs = { "opengl", + "sdl", -- key definitions "spidermonkey", -- for graphics/scripting "boost" } @@ -262,6 +271,7 @@ function setup_all_libs () } extern_libs = { "boost", + "sdl", -- key definitions "opengl", "spidermonkey" } @@ -274,6 +284,7 @@ function setup_all_libs () } extern_libs = { "spidermonkey", + "sdl", -- key definitions "opengl", "boost" } @@ -289,6 +300,7 @@ function setup_all_libs () "lib/res/sound" } extern_libs = { + "sdl", "opengl", "libpng", "zlib", @@ -406,15 +418,17 @@ end -------------------------------------------------------------------------------- -- setup a typical Atlas component package -local function setup_atlas_package(package_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, flags) +-- extra_params: as in package_add_contents; also zero or more of the following: +-- * pch: (any type) set stdafx.h and .cpp as PCH +local function setup_atlas_package(package_name, target_type, rel_source_dirs, rel_include_dirs, extern_libs, extra_params) local source_root = "../../../source/tools/atlas/" .. package_name .. "/" package_create(package_name, target_type) -- Don't add the default 'sourceroot/pch/projectname' for finding PCH files - flags["no_default_pch"] = 1 + extra_params["no_default_pch"] = 1 - package_add_contents(source_root, rel_source_dirs, rel_include_dirs, flags) + package_add_contents(source_root, rel_source_dirs, rel_include_dirs, extra_params) package_add_extern_libs(extern_libs) -- Platform Specifics @@ -428,15 +442,11 @@ local function setup_atlas_package(package_name, target_type, rel_source_dirs, r -- required to use WinMain() on Windows, otherwise will default to main() tinsert(package.buildflags, "no-main") - if flags["pch"] then + if extra_params["pch"] then package.pchheader = "stdafx.h" package.pchsource = "stdafx.cpp" end - if flags["depends"] then - listconcat(package.links, flags["depends"]) - end - else -- Non-Windows, = Unix -- TODO end @@ -453,7 +463,7 @@ function setup_atlas_packages() },{ -- include },{ -- extern_libs "xerces" - },{ -- flags + },{ -- extra_params }) setup_atlas_package("AtlasUI", "dll", @@ -488,9 +498,9 @@ function setup_atlas_packages() "devil", "xerces", "wxwidgets" - },{ -- flags + },{ -- extra_params pch = 1, - depends = { "AtlasObject", "DatafileIO" }, + extra_links = { "AtlasObject", "DatafileIO" }, extra_files = { "Misc/atlas.rc" } }) @@ -507,7 +517,7 @@ function setup_atlas_packages() "devil", "xerces", "zlib" - },{ -- flags + },{ -- extra_params pch = 1, }) diff --git a/source/lib/adts.cpp b/source/lib/adts.cpp deleted file mode 100644 index a6937b09fc..0000000000 --- a/source/lib/adts.cpp +++ /dev/null @@ -1,25 +0,0 @@ -/** - * ========================================================================= - * File : adts.cpp - * Project : 0 A.D. - * Description : useful Abstract Data Types not provided by STL. - * - * @author Jan.Wassenberg@stud.uni-karlsruhe.de - * ========================================================================= - */ - -/* - * Copyright (c) 2005 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. - */ - -#include "precompiled.h" - -#include "adts.h" diff --git a/source/lib/adts.h b/source/lib/adts.h index 2bd7ef81e9..d65f6d57e8 100644 --- a/source/lib/adts.h +++ b/source/lib/adts.h @@ -23,15 +23,9 @@ #ifndef ADTS_H__ #define ADTS_H__ -#include "lib.h" - -#include - -#pragma warning(push, 3) // VC7.1 STL not /W4 clean :( -#include -#include -#pragma warning(pop) - +//----------------------------------------------------------------------------- +// dynamic (grow-able) hash table +//----------------------------------------------------------------------------- template class DHT_Traits { @@ -61,7 +55,6 @@ public: }; - // intended for pointer types template > class DynHashTbl @@ -241,712 +234,8 @@ public: //----------------------------------------------------------------------------- - -/* -Cache for items of variable size and value/"cost". -underlying displacement algorithm is pluggable; default is "Landlord". - -template reference: -Entry provides size, cost, credit and credit_density(). - rationale: - - made a template instead of exposing Cache::Entry because - that would drag a lot of stuff out of Cache. - - calculates its own density since that entails a Divider functor, - which requires storage inside Entry. -Entries is a collection with iterator and begin()/end() and - "static Entry& entry_from_it(iterator)". - rationale: - - STL map has pair as its value_type, so this - function would return it->second. however, we want to support - other container types (where we'd just return *it). -Manager is a template parameterized on typename Key and class Entry. - its interface is as follows: - - // is the cache empty? - bool empty() const; - - // add (key, entry) to cache. - void add(Key key, const Entry& entry); - - // if the entry identified by is not in cache, return false; - // otherwise return true and pass back a pointer to it. - bool find(Key key, const Entry** pentry) const; - - // remove an entry from cache, which is assumed to exist! - // this makes sense because callers will typically first use find() to - // return info about the entry; this also checks if present. - void remove(Key key); - - // mark as just accessed for purpose of cache management. - // it will tend to be kept in cache longer. - void on_access(Entry& entry); - - // caller's intent is to remove the least valuable entry. - // in implementing this, you have the latitude to "shake loose" - // several entries (e.g. because their 'value' is equal). - // they must all be push_back-ed into the list; Cache will dole - // them out one at a time in FIFO order to callers. - // - // rationale: - // - it is necessary for callers to receive a copy of the - // Entry being evicted - e.g. file_cache owns its items and - // they must be passed back to allocator when evicted. - // - e.g. Landlord can potentially see several entries become - // evictable in one call to remove_least_valuable. there are - // several ways to deal with this: - // 1) generator interface: we return one of { empty, nevermind, - // removed, remove-and-call-again }. this greatly complicates - // the call site. - // 2) return immediately after finding an item to evict. - // this changes cache behavior - entries stored at the - // beginning would be charged more often (unfair). - // resuming charging at the next entry doesn't work - this - // would have to be flushed when adding, at which time there - // is no provision for returning any items that may be evicted. - // 3) return list of all entries "shaken loose". this incurs - // frequent mem allocs, which can be alleviated via suballocator. - // note: an intrusive linked-list doesn't make sense because - // entries to be returned need to be copied anyway (they are - // removed from the manager's storage). - void remove_least_valuable(std::list& entry_list) -*/ - - -// -// functors to calculate minimum credit density (MCD) -// - -// MCD is required for the Landlord algorithm's evict logic. -// [Young02] calls it '\delta'. - -// scan over all entries and return MCD. -template float ll_calc_min_credit_density(const Entries& entries) -{ - float min_credit_density = FLT_MAX; - for(typename Entries::const_iterator it = entries.begin(); it != entries.end(); ++it) - { - const float credit_density = Entries::entry_from_it(it).credit_density(); - min_credit_density = fminf(min_credit_density, credit_density); - } - return min_credit_density; -} - -// note: no warning is given that the MCD entry is being removed! -// (reduces overhead in remove_least_valuable) -// these functors must account for that themselves (e.g. by resetting -// their state directly after returning MCD). - -// determine MCD by scanning over all entries. -// tradeoff: O(N) time complexity, but all notify* calls are no-ops. -template -class McdCalc_Naive -{ -public: - void notify_added(const Entry&) const {} - void notify_decreased(const Entry&) const {} - void notify_impending_increase_or_remove(const Entry&) const {} - void notify_increased_or_removed(const Entry&) const {} - float operator()(const Entries& entries) const - { - const float mcd = ll_calc_min_credit_density(entries); - return mcd; - } -}; - -// cache previous MCD and update it incrementally (when possible). -// tradeoff: amortized O(1) time complexity, but notify* calls must -// perform work whenever something in the cache changes. -template -class McdCalc_Cached -{ -public: - McdCalc_Cached() : min_credit_density(FLT_MAX), min_valid(false) {} - - void notify_added(const Entry& entry) - { - // when adding a new item, the minimum credit density can only - // decrease or remain the same; acting as if entry's credit had - // been decreased covers both cases. - notify_decreased(entry); - } - - void notify_decreased(const Entry& entry) - { - min_credit_density = MIN(min_credit_density, entry.credit_density()); - } - - void notify_impending_increase_or_remove(const Entry& entry) - { - // remember if this entry had the smallest density - is_min_entry = feq(min_credit_density, entry.credit_density()); - } - - void notify_increased_or_removed(const Entry& UNUSED(entry)) - { - // .. it did and was increased or removed. we must invalidate - // MCD and recalculate it next time. - if(is_min_entry) - { - min_valid = false; - min_credit_density = -1.0f; - } - } - - float operator()(const Entries& entries) - { - if(min_valid) - { - // the entry that has MCD will be removed anyway by caller; - // we need to invalidate here because they don't call - // notify_increased_or_removed. - min_valid = false; - return min_credit_density; - } - - // this is somewhat counterintuitive. since we're calculating - // MCD directly, why not mark our cached version of it valid - // afterwards? reason is that our caller will remove the entry with - // MCD, so it'll be invalidated anyway. - // instead, our intent is to calculate MCD for the *next time*. - const float ret = ll_calc_min_credit_density(entries); - min_valid = true; - min_credit_density = FLT_MAX; - return ret; - } - -private: - float min_credit_density; - bool min_valid; - - // temporary flag set by notify_impending_increase_or_remove - bool is_min_entry; -}; - - -// -// Landlord cache management policy: see [Young02]. -// - -// in short, each entry has credit initially set to cost. when wanting to -// remove an item, all are charged according to MCD and their size; -// entries are evicted if their credit is exhausted. accessing an entry -// restores "some" of its credit. -template class McdCalc = McdCalc_Cached> -class Landlord -{ -public: - bool empty() const - { - return map.empty(); - } - - void add(Key key, const Entry& entry) - { - // adapter for add_ (which returns an iterator) - (void)add_(key, entry); - } - - bool find(Key key, const Entry** pentry) const - { - MapCIt it = map.find(key); - if(it == map.end()) - return false; - *pentry = &it->second; - return true; - } - - void remove(Key key) - { - MapIt it = map.find(key); - debug_assert(it != map.end()); - remove_(it); - } - - void on_access(Entry& entry) - { - mcd_calc.notify_impending_increase_or_remove(entry); - - // Landlord algorithm calls for credit to be reset to anything - // between its current value and the cost. - const float gain = 0.75f; // restore most credit - entry.credit = gain*entry.cost + (1.0f-gain)*entry.credit; - - mcd_calc.notify_increased_or_removed(entry); - } - - void remove_least_valuable(std::list& entry_list) - { - // we are required to evict at least one entry. one iteration - // ought to suffice, due to definition of min_credit_density and - // tolerance; however, we provide for repeating if necessary. -again: - - // messing with this (e.g. raising if tiny) would result in - // different evictions than Landlord_Lazy, which is unacceptable. - // nor is doing so necessary: if mcd is tiny, so is credit. - const float min_credit_density = mcd_calc(map); - debug_assert(min_credit_density > 0.0f); - - for(MapIt it = map.begin(); it != map.end();) // no ++it - { - Entry& entry = it->second; - - charge(entry, min_credit_density); - if(should_evict(entry)) - { - entry_list.push_back(entry); - - // annoying: we have to increment before erasing - MapIt it_to_remove = it++; - map.erase(it_to_remove); - } - else - { - mcd_calc.notify_decreased(entry); - ++it; - } - } - - if(entry_list.empty()) - goto again; - } - -protected: - // note: use hash_map instead of map for better locality - // (relevant when iterating over all items in remove_least_valuable) - class Map : public STL_HASH_MAP - { - public: - static Entry& entry_from_it(typename Map::iterator it) { return it->second; } - static const Entry& entry_from_it(typename Map::const_iterator it) { return it->second; } - }; - typedef typename Map::iterator MapIt; - typedef typename Map::const_iterator MapCIt; - Map map; - - // add entry and return iterator pointing to it. - MapIt add_(Key key, const Entry& entry) - { - typedef std::pair PairIB; - typename Map::value_type val = std::make_pair(key, entry); - PairIB ret = map.insert(val); - debug_assert(ret.second); // must not already be in map - - mcd_calc.notify_added(entry); - - return ret.first; - } - - // remove entry (given by iterator) directly. - void remove_(MapIt it) - { - const Entry& entry = it->second; - mcd_calc.notify_impending_increase_or_remove(entry); - mcd_calc.notify_increased_or_removed(entry); - map.erase(it); - } - - void charge(Entry& entry, float delta) - { - entry.credit -= delta * entry.size; - - // don't worry about entry.size being 0 - if so, cost - // should also be 0, so credit will already be 0 anyway. - } - - // for each entry, 'charge' it (i.e. reduce credit by) delta * its size. - // delta is typically MCD (see above); however, several such updates - // may be lumped together to save time. Landlord_Lazy does this. - void charge_all(float delta) - { - for(MapIt it = map.begin(); it != map.end(); ++it) - { - Entry& entry = it->second; - entry.credit -= delta * entry.size; - if(!should_evict(entry)) - mcd_calc.notify_decreased(entry); - } - } - - // is entry's credit exhausted? if so, it should be evicted. - bool should_evict(const Entry& entry) - { - // we need a bit of leeway because density calculations may not - // be exact. choose value carefully: must not be high enough to - // trigger false positives. - return entry.credit < 0.0001f; - } - -private: - McdCalc mcd_calc; -}; - -// Cache manger policies. (these are partial specializations of Landlord, -// adapting it to the template params required by Cache) -template class Landlord_Naive : public Landlord {}; -template class Landlord_Cached: public Landlord {}; - -// variant of Landlord that adds a priority queue to directly determine -// which entry to evict. this allows lumping several charge operations -// together and thus reduces iteration over all entries. -// tradeoff: O(logN) removal (instead of N), but additional O(N) storage. -template -class Landlord_Lazy : public Landlord_Naive -{ - typedef typename Landlord_Naive::Map Map; - typedef typename Landlord_Naive::MapIt MapIt; - typedef typename Landlord_Naive::MapCIt MapCIt; - -public: - Landlord_Lazy() { pending_delta = 0.0f; } - - void add(Key key, const Entry& entry) - { - // we must apply pending_delta now - otherwise, the existing delta - // would later be applied to this newly added item (incorrect). - commit_pending_delta(); - - MapIt it = Parent::add_(key, entry); - pri_q.push(it); - } - - void remove(Key key) - { - Parent::remove(key); - - // reconstruct pri_q from current map. this is slow (N*logN) and - // could definitely be done better, but we don't bother since - // remove is a very rare operation (e.g. invalidating entries). - while(!pri_q.empty()) - pri_q.pop(); - for(MapCIt it = this->map.begin(); it != this->map.end(); ++it) - pri_q.push(it); - } - - void on_access(Entry& entry) - { - Parent::on_access(entry); - - // entry's credit was changed. we now need to reshuffle the - // pri queue to reflect this. - pri_q.ensure_heap_order(); - } - - void remove_least_valuable(std::list& entry_list) - { - MapIt least_valuable_it = pri_q.top(); pri_q.pop(); - Entry& entry = Map::entry_from_it(least_valuable_it); - - entry_list.push_back(entry); - - // add to pending_delta the MCD that would have resulted - // if removing least_valuable_it normally. - // first, calculate actual credit (i.e. apply pending_delta to - // this entry); then add the resulting density to pending_delta. - entry.credit -= pending_delta*entry.size; - const float credit_density = entry.credit_density(); - debug_assert(credit_density > 0.0f); - pending_delta += credit_density; - - Parent::remove_(least_valuable_it); - } - -private: - typedef Landlord_Naive Parent; - - // sort iterators by credit_density of the Entry they reference. - struct CD_greater - { - bool operator()(MapIt it1, MapIt it2) const - { - return Map::entry_from_it(it1).credit_density() > - Map::entry_from_it(it2).credit_density(); - } - }; - // wrapper on top of priority_queue that allows 'heap re-sift' - // (see on_access). - // notes: - // - greater comparator makes pri_q.top() the one with - // LEAST credit_density, which is what we want. - // - deriving from an STL container is a bit dirty, but we need this - // to get at the underlying data (priority_queue interface is not - // very capable). - class PriQ: public std::priority_queue, CD_greater> - { - public: - void ensure_heap_order() - { - // TODO: this is actually N*logN - ouch! that explains high - // CPU cost in profile. this is called after only 1 item has - // changed, so a logN "sift" operation ought to suffice. - // that's not supported by the STL heap functions, so we'd - // need a better implementation. pending.. - std::make_heap(this->c.begin(), this->c.end(), this->comp); - } - }; - PriQ pri_q; - - // delta values that have accumulated over several - // remove_least_valuable() calls. applied during add(). - float pending_delta; - - void commit_pending_delta() - { - if(pending_delta > 0.0f) - { - this->charge_all(pending_delta); - pending_delta = 0.0f; - - // we've changed entry credit, so the heap order *may* have been - // violated; reorder the pri queue. (I don't think so, - // due to definition of delta, but we'll play it safe) - pri_q.ensure_heap_order(); - } - } -}; - - -// -// functor that implements division of first arg by second -// - -// this is used to calculate credit_density(); performance matters -// because this is called for each entry during each remove operation. - -// floating-point division (fairly slow) -class Divider_Naive -{ -public: - Divider_Naive() {} // needed for default CacheEntry ctor - Divider_Naive(float UNUSED(x)) {} - float operator()(float val, float divisor) const - { - return val / divisor; - } -}; - -// caches reciprocal of divisor and multiplies by that. -// tradeoff: only 4 clocks (instead of 20), but 4 bytes extra per entry. -class Divider_Recip -{ - float recip; -public: - Divider_Recip() {} // needed for default CacheEntry ctor - Divider_Recip(float x) { recip = 1.0f / x; } - float operator()(float val, float UNUSED(divisor)) const - { - return val * recip; - } -}; - -// TODO: use SSE/3DNow RCP instruction? not yet, because not all systems -// support it and overhead of detecting this support eats into any gains. - -// initial implementation for testing purposes; quite inefficient. -template -class LRU -{ -public: - bool empty() const - { - return lru.empty(); - } - - void add(Key key, const Entry& entry) - { - lru.push_back(KeyAndEntry(key, entry)); - } - - bool find(Key key, const Entry** pentry) const - { - CIt it = std::find_if(lru.begin(), lru.end(), KeyEq(key)); - if(it == lru.end()) - return false; - *pentry = &it->entry; - return true; - } - - void remove(Key key) - { - std::remove_if(lru.begin(), lru.end(), KeyEq(key)); - } - - void on_access(Entry& entry) - { - for(It it = lru.begin(); it != lru.end(); ++it) - { - if(&entry == &it->entry) - { - add(it->key, it->entry); - lru.erase(it); - return; - } - } - debug_warn("entry not found in list"); - } - - void remove_least_valuable(std::list& entry_list) - { - entry_list.push_back(lru.front().entry); - lru.pop_front(); - } - -private: - struct KeyAndEntry - { - Key key; - Entry entry; - KeyAndEntry(Key key_, const Entry& entry_) - : key(key_), entry(entry_) {} - }; - class KeyEq - { - Key key; - public: - KeyEq(Key key_) : key(key_) {} - bool operator()(const KeyAndEntry& ke) const - { - return ke.key == key; - } - }; - - typedef std::list List; - typedef typename List::iterator It; - typedef typename List::const_iterator CIt; - List lru; -}; - - -// -// Cache -// - -template -< -typename Key, typename Item, -// see documentation above for Manager's interface. -template class Manager = Landlord_Cached, -class Divider = Divider_Naive -> -class Cache -{ -public: - Cache() : mgr() {} - - void add(Key key, Item item, size_t size, uint cost) - { - return mgr.add(key, Entry(item, size, cost)); - } - - // remove the entry identified by . expected usage is to check - // if present and determine size via retrieve(), so no need for - // error checking. - // useful for invalidating single cache entries. - void remove(Key key) - { - mgr.remove(key); - } - - // if there is no entry for in the cache, return false. - // otherwise, return true and pass back item and (optionally) size. - // - // if refill_credit (default), the cache manager 'rewards' this entry, - // tending to keep it in cache longer. this parameter is not used in - // normal operation - it's only for special cases where we need to - // make an end run around the cache accounting (e.g. for cache simulator). - bool retrieve(Key key, Item& item, size_t* psize = 0, bool refill_credit = true) - { - const Entry* entry; - if(!mgr.find(key, &entry)) - return false; - - item = entry->item; - if(psize) - *psize = entry->size; - - if(refill_credit) - mgr.on_access((Entry&)*entry); - - return true; - } - - // toss out the least valuable entry. return false if cache is empty, - // otherwise true and (optionally) pass back its item and size. - bool remove_least_valuable(Item* pItem = 0, size_t* pSize = 0) - { - // as an artefact of the cache eviction policy, several entries - // may be "shaken loose" by one call to remove_least_valuable. - // we cache them in a list to disburden callers (they always get - // exactly one). - if(entries_awaiting_eviction.empty()) - { - if(empty()) - return false; - - mgr.remove_least_valuable(entries_awaiting_eviction); - debug_assert(!entries_awaiting_eviction.empty()); - } - - const Entry& entry = entries_awaiting_eviction.front(); - if(pItem) - *pItem = entry.item; - if(pSize) - *pSize = entry.size; - entries_awaiting_eviction.pop_front(); - - return true; - } - - bool empty() const - { - return mgr.empty(); - } - -private: - // this is applicable to all cache management policies and stores all - // required information. a Divider functor is used to implement - // division for credit_density. - template struct CacheEntry - { - Item item; - size_t size; - uint cost; - float credit; - - InnerDivider divider; - - // needed for mgr.remove_least_valuable's entry_copy - CacheEntry() {} - - CacheEntry(Item item_, size_t size_, uint cost_) - : item(item_), divider((float)size_) - { - size = size_; - cost = cost_; - credit = cost; - - // else divider will fail - debug_assert(size != 0); - } - - float credit_density() const - { - return divider(credit, (float)size); - } - }; - typedef CacheEntry Entry; - - // see note in remove_least_valuable(). - std::list entries_awaiting_eviction; - - Manager mgr; -}; - - - -// // FIFO bit queue -// +//----------------------------------------------------------------------------- struct BitBuf { @@ -981,9 +270,9 @@ struct BitBuf }; -// +//----------------------------------------------------------------------------- // ring buffer - static array, accessible modulo n -// +//----------------------------------------------------------------------------- template class RingBuf { @@ -1168,143 +457,4 @@ public: }; - -// -// cache -// - - -// owns a pool of resources (Entry-s), associated with a 64 bit id. -// typical use: add all available resources to the cache via grow(); -// assign() ids to the resources, and update the resource data if necessary; -// retrieve() the resource, given id. -template class LRUCache -{ -public: - // 'give' Entry to the cache. - int grow(Entry& e) - { - // add to front of LRU list, but not index - // (since we don't have an id yet) - lru_list.push_front(Line(e)); - return 0; - } - - - // find the least-recently used line; associate id with it, - // and return its Entry. fails (returns 0) if id is already - // associated, or all lines are locked. - Entry* assign(u64 id) - { - if(find_line(id)) - { - debug_warn("assign: id already in cache!"); - return 0; - } - - // scan in least->most used order for first non-locked entry - List_iterator l = lru_list.end(); - while(l != lru_list.begin()) - { - --l; - if(l->refs == 0) - goto have_line; - } - - // all are locked and cannot be displaced. - // caller should grow() enough lines so that this never happens. - debug_warn("assign: all lines locked - grow() more lines"); - return 0; - - have_line: - - // update mapping (index) - idx.erase(id); - idx[id] = l; - - l->id = id; - return &l->ent; - } - - - // find line identified by id; return its entry or 0 if not in cache. - Entry* retrieve(u64 id) - { - // invalid: id 0 denotes not-yet-associated lines - if(id == 0) - { - debug_warn("retrieve: id 0 not allowed"); - return 0; - } - Line* l = find_line(id); - return l? &l->ent : 0; - } - - - // add/release a reference to a line, to protect it against - // displacement via associate(). we verify refs >= 0. - int lock(u64 id, bool locked) - { - Line* l = find_line(id); - if(!l) - return -1; - - if(locked) - l->refs++; - else - { - debug_assert(l->refs > 0); - l->refs--; - } - return 0; - } - - -private: - // implementation: - // cache lines are stored in a list, most recently used in front. - // a map finds the list entry containing a given id in log-time. - - struct Line - { - u64 id; - Entry ent; - int refs; // protect from displacement if > 0 - - Line(Entry& _ent) - { - id = 0; - ent = _ent; - refs = 0; - } - }; - - typedef std::list List; - typedef typename List::iterator List_iterator; - List lru_list; - - typedef std::map Map; - Map idx; - - - // return the line identified by id, or 0 if not in cache. - // mark it as the most recently used line. - Line* find_line(u64 id) - { - typename Map::const_iterator i = idx.find(id); - // not found - if(i == idx.end()) - return 0; - - // index points us to list entry - List_iterator l = i->second; - - // mark l as the most recently used line. - lru_list.splice(lru_list.begin(), lru_list, l); - idx[l->id] = l; - - return &*l; - } -}; - #endif // #ifndef ADTS_H__ diff --git a/source/lib/app_hooks.cpp b/source/lib/app_hooks.cpp index 44bb8eaac8..a1e01c700d 100644 --- a/source/lib/app_hooks.cpp +++ b/source/lib/app_hooks.cpp @@ -109,32 +109,9 @@ static void bundle_logs(FILE* f) } -// TODO: leaks memory returned by wcsdup + static const wchar_t* translate(const wchar_t* text) { -#if HAVE_I18N - // make sure i18n system is (already|still) initialized. - if(g_CurrentLocale) - { - // be prepared for this to fail, because translation potentially - // involves script code and the JS context might be corrupted. -#if OS_WIN - __try -#endif - { - const wchar_t* text2 = wcsdup(I18n::translate(text).c_str()); - // only overwrite if wcsdup succeeded, i.e. not out of memory. - if(text2) - text = text2; - } -#if OS_WIN - __except(EXCEPTION_EXECUTE_HANDLER) -#endif - { - } - } -#endif - return text; } diff --git a/source/lib/app_hooks.h b/source/lib/app_hooks.h index 777c9a17c1..b5e17ba07d 100644 --- a/source/lib/app_hooks.h +++ b/source/lib/app_hooks.h @@ -73,6 +73,10 @@ and call set_app_hooks. // a return value. #ifdef FUNC +// for convenience; less confusing than FUNC(void, [..], (void)) +#define VOID_FUNC(name, params, param_names)\ + FUNC(void, name, params, param_names, (void)) + // override default decision on using OpenGL extensions relating to // texture upload. this should call ogl_tex_override to disable/force // their use if the current card/driver combo respectively crashes or @@ -80,7 +84,7 @@ and call set_app_hooks. // // default implementation works but is hardwired in code and therefore // not expandable. -FUNC(void, override_gl_upload_caps, (void), (), (void)) +VOID_FUNC(override_gl_upload_caps, (void), ()) // return full native path of the directory into which crashdumps should be // written. must end with directory separator (e.g. '/'). @@ -95,7 +99,7 @@ FUNC(const char*, get_log_dir, (void), (), return) // used when writing a crashlog so that all relevant info is in one file. // // default implementation gathers 0ad data but is fail-safe. -FUNC(void, bundle_logs, (FILE* f), (f), (void)) +VOID_FUNC(bundle_logs, (FILE* f), (f)) // return localized version of if i18n functionality is available. // @@ -105,10 +109,12 @@ FUNC(const wchar_t*, translate, (const wchar_t* text), (text), return) // write to the app's log. // // default implementation uses stdout. -FUNC(void, log, (const wchar_t* text), (text), (void)) +VOID_FUNC(log, (const wchar_t* text), (text)) FUNC(ErrorReaction, display_error, (const wchar_t* text, uint flags), (text, flags), return) +#undef VOID_FUNC + #endif // #ifdef FUNC diff --git a/source/lib/cache_adt.h b/source/lib/cache_adt.h new file mode 100644 index 0000000000..8179d08d9f --- /dev/null +++ b/source/lib/cache_adt.h @@ -0,0 +1,731 @@ +/** + * ========================================================================= + * File : cache_adt.h + * Project : 0 A.D. + * Description : Customizable cache data structure. + * + * @author Jan.Wassenberg@stud.uni-karlsruhe.de + * ========================================================================= + */ + +/* + * Copyright (c) 2005-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 CACHE_ADT_H__ +#define CACHE_ADT_H__ + +#include + +#include +#include + +/* +Cache for items of variable size and value/"cost". +underlying displacement algorithm is pluggable; default is "Landlord". + +template reference: +Entry provides size, cost, credit and credit_density(). + rationale: + - made a template instead of exposing Cache::Entry because + that would drag a lot of stuff out of Cache. + - calculates its own density since that entails a Divider functor, + which requires storage inside Entry. +Entries is a collection with iterator and begin()/end() and + "static Entry& entry_from_it(iterator)". + rationale: + - STL map has pair as its value_type, so this + function would return it->second. however, we want to support + other container types (where we'd just return *it). +Manager is a template parameterized on typename Key and class Entry. + its interface is as follows: + + // is the cache empty? + bool empty() const; + + // add (key, entry) to cache. + void add(Key key, const Entry& entry); + + // if the entry identified by is not in cache, return false; + // otherwise return true and pass back a pointer to it. + bool find(Key key, const Entry** pentry) const; + + // remove an entry from cache, which is assumed to exist! + // this makes sense because callers will typically first use find() to + // return info about the entry; this also checks if present. + void remove(Key key); + + // mark as just accessed for purpose of cache management. + // it will tend to be kept in cache longer. + void on_access(Entry& entry); + + // caller's intent is to remove the least valuable entry. + // in implementing this, you have the latitude to "shake loose" + // several entries (e.g. because their 'value' is equal). + // they must all be push_back-ed into the list; Cache will dole + // them out one at a time in FIFO order to callers. + // + // rationale: + // - it is necessary for callers to receive a copy of the + // Entry being evicted - e.g. file_cache owns its items and + // they must be passed back to allocator when evicted. + // - e.g. Landlord can potentially see several entries become + // evictable in one call to remove_least_valuable. there are + // several ways to deal with this: + // 1) generator interface: we return one of { empty, nevermind, + // removed, remove-and-call-again }. this greatly complicates + // the call site. + // 2) return immediately after finding an item to evict. + // this changes cache behavior - entries stored at the + // beginning would be charged more often (unfair). + // resuming charging at the next entry doesn't work - this + // would have to be flushed when adding, at which time there + // is no provision for returning any items that may be evicted. + // 3) return list of all entries "shaken loose". this incurs + // frequent mem allocs, which can be alleviated via suballocator. + // note: an intrusive linked-list doesn't make sense because + // entries to be returned need to be copied anyway (they are + // removed from the manager's storage). + void remove_least_valuable(std::list& entry_list) +*/ + + +// +// functors to calculate minimum credit density (MCD) +// + +// MCD is required for the Landlord algorithm's evict logic. +// [Young02] calls it '\delta'. + +// scan over all entries and return MCD. +template float ll_calc_min_credit_density(const Entries& entries) +{ + float min_credit_density = FLT_MAX; + for(typename Entries::const_iterator it = entries.begin(); it != entries.end(); ++it) + { + const float credit_density = Entries::entry_from_it(it).credit_density(); + min_credit_density = fminf(min_credit_density, credit_density); + } + return min_credit_density; +} + +// note: no warning is given that the MCD entry is being removed! +// (reduces overhead in remove_least_valuable) +// these functors must account for that themselves (e.g. by resetting +// their state directly after returning MCD). + +// determine MCD by scanning over all entries. +// tradeoff: O(N) time complexity, but all notify* calls are no-ops. +template +class McdCalc_Naive +{ +public: + void notify_added(const Entry&) const {} + void notify_decreased(const Entry&) const {} + void notify_impending_increase_or_remove(const Entry&) const {} + void notify_increased_or_removed(const Entry&) const {} + float operator()(const Entries& entries) const + { + const float mcd = ll_calc_min_credit_density(entries); + return mcd; + } +}; + +// cache previous MCD and update it incrementally (when possible). +// tradeoff: amortized O(1) time complexity, but notify* calls must +// perform work whenever something in the cache changes. +template +class McdCalc_Cached +{ +public: + McdCalc_Cached() : min_credit_density(FLT_MAX), min_valid(false) {} + + void notify_added(const Entry& entry) + { + // when adding a new item, the minimum credit density can only + // decrease or remain the same; acting as if entry's credit had + // been decreased covers both cases. + notify_decreased(entry); + } + + void notify_decreased(const Entry& entry) + { + min_credit_density = MIN(min_credit_density, entry.credit_density()); + } + + void notify_impending_increase_or_remove(const Entry& entry) + { + // remember if this entry had the smallest density + is_min_entry = feq(min_credit_density, entry.credit_density()); + } + + void notify_increased_or_removed(const Entry& UNUSED(entry)) + { + // .. it did and was increased or removed. we must invalidate + // MCD and recalculate it next time. + if(is_min_entry) + { + min_valid = false; + min_credit_density = -1.0f; + } + } + + float operator()(const Entries& entries) + { + if(min_valid) + { + // the entry that has MCD will be removed anyway by caller; + // we need to invalidate here because they don't call + // notify_increased_or_removed. + min_valid = false; + return min_credit_density; + } + + // this is somewhat counterintuitive. since we're calculating + // MCD directly, why not mark our cached version of it valid + // afterwards? reason is that our caller will remove the entry with + // MCD, so it'll be invalidated anyway. + // instead, our intent is to calculate MCD for the *next time*. + const float ret = ll_calc_min_credit_density(entries); + min_valid = true; + min_credit_density = FLT_MAX; + return ret; + } + +private: + float min_credit_density; + bool min_valid; + + // temporary flag set by notify_impending_increase_or_remove + bool is_min_entry; +}; + + +// +// Landlord cache management policy: see [Young02]. +// + +// in short, each entry has credit initially set to cost. when wanting to +// remove an item, all are charged according to MCD and their size; +// entries are evicted if their credit is exhausted. accessing an entry +// restores "some" of its credit. +template class McdCalc = McdCalc_Cached> +class Landlord +{ +public: + bool empty() const + { + return map.empty(); + } + + void add(Key key, const Entry& entry) + { + // adapter for add_ (which returns an iterator) + (void)add_(key, entry); + } + + bool find(Key key, const Entry** pentry) const + { + MapCIt it = map.find(key); + if(it == map.end()) + return false; + *pentry = &it->second; + return true; + } + + void remove(Key key) + { + MapIt it = map.find(key); + debug_assert(it != map.end()); + remove_(it); + } + + void on_access(Entry& entry) + { + mcd_calc.notify_impending_increase_or_remove(entry); + + // Landlord algorithm calls for credit to be reset to anything + // between its current value and the cost. + const float gain = 0.75f; // restore most credit + entry.credit = gain*entry.cost + (1.0f-gain)*entry.credit; + + mcd_calc.notify_increased_or_removed(entry); + } + + void remove_least_valuable(std::list& entry_list) + { + // we are required to evict at least one entry. one iteration + // ought to suffice, due to definition of min_credit_density and + // tolerance; however, we provide for repeating if necessary. +again: + + // messing with this (e.g. raising if tiny) would result in + // different evictions than Landlord_Lazy, which is unacceptable. + // nor is doing so necessary: if mcd is tiny, so is credit. + const float min_credit_density = mcd_calc(map); + debug_assert(min_credit_density > 0.0f); + + for(MapIt it = map.begin(); it != map.end();) // no ++it + { + Entry& entry = it->second; + + charge(entry, min_credit_density); + if(should_evict(entry)) + { + entry_list.push_back(entry); + + // annoying: we have to increment before erasing + MapIt it_to_remove = it++; + map.erase(it_to_remove); + } + else + { + mcd_calc.notify_decreased(entry); + ++it; + } + } + + if(entry_list.empty()) + goto again; + } + +protected: + // note: use hash_map instead of map for better locality + // (relevant when iterating over all items in remove_least_valuable) + class Map : public STL_HASH_MAP + { + public: + static Entry& entry_from_it(typename Map::iterator it) { return it->second; } + static const Entry& entry_from_it(typename Map::const_iterator it) { return it->second; } + }; + typedef typename Map::iterator MapIt; + typedef typename Map::const_iterator MapCIt; + Map map; + + // add entry and return iterator pointing to it. + MapIt add_(Key key, const Entry& entry) + { + typedef std::pair PairIB; + typename Map::value_type val = std::make_pair(key, entry); + PairIB ret = map.insert(val); + debug_assert(ret.second); // must not already be in map + + mcd_calc.notify_added(entry); + + return ret.first; + } + + // remove entry (given by iterator) directly. + void remove_(MapIt it) + { + const Entry& entry = it->second; + mcd_calc.notify_impending_increase_or_remove(entry); + mcd_calc.notify_increased_or_removed(entry); + map.erase(it); + } + + void charge(Entry& entry, float delta) + { + entry.credit -= delta * entry.size; + + // don't worry about entry.size being 0 - if so, cost + // should also be 0, so credit will already be 0 anyway. + } + + // for each entry, 'charge' it (i.e. reduce credit by) delta * its size. + // delta is typically MCD (see above); however, several such updates + // may be lumped together to save time. Landlord_Lazy does this. + void charge_all(float delta) + { + for(MapIt it = map.begin(); it != map.end(); ++it) + { + Entry& entry = it->second; + entry.credit -= delta * entry.size; + if(!should_evict(entry)) + mcd_calc.notify_decreased(entry); + } + } + + // is entry's credit exhausted? if so, it should be evicted. + bool should_evict(const Entry& entry) + { + // we need a bit of leeway because density calculations may not + // be exact. choose value carefully: must not be high enough to + // trigger false positives. + return entry.credit < 0.0001f; + } + +private: + McdCalc mcd_calc; +}; + +// Cache manger policies. (these are partial specializations of Landlord, +// adapting it to the template params required by Cache) +template class Landlord_Naive : public Landlord {}; +template class Landlord_Cached: public Landlord {}; + +// variant of Landlord that adds a priority queue to directly determine +// which entry to evict. this allows lumping several charge operations +// together and thus reduces iteration over all entries. +// tradeoff: O(logN) removal (instead of N), but additional O(N) storage. +template +class Landlord_Lazy : public Landlord_Naive +{ + typedef typename Landlord_Naive::Map Map; + typedef typename Landlord_Naive::MapIt MapIt; + typedef typename Landlord_Naive::MapCIt MapCIt; + +public: + Landlord_Lazy() { pending_delta = 0.0f; } + + void add(Key key, const Entry& entry) + { + // we must apply pending_delta now - otherwise, the existing delta + // would later be applied to this newly added item (incorrect). + commit_pending_delta(); + + MapIt it = Parent::add_(key, entry); + pri_q.push(it); + } + + void remove(Key key) + { + Parent::remove(key); + + // reconstruct pri_q from current map. this is slow (N*logN) and + // could definitely be done better, but we don't bother since + // remove is a very rare operation (e.g. invalidating entries). + while(!pri_q.empty()) + pri_q.pop(); + for(MapCIt it = this->map.begin(); it != this->map.end(); ++it) + pri_q.push(it); + } + + void on_access(Entry& entry) + { + Parent::on_access(entry); + + // entry's credit was changed. we now need to reshuffle the + // pri queue to reflect this. + pri_q.ensure_heap_order(); + } + + void remove_least_valuable(std::list& entry_list) + { + MapIt least_valuable_it = pri_q.top(); pri_q.pop(); + Entry& entry = Map::entry_from_it(least_valuable_it); + + entry_list.push_back(entry); + + // add to pending_delta the MCD that would have resulted + // if removing least_valuable_it normally. + // first, calculate actual credit (i.e. apply pending_delta to + // this entry); then add the resulting density to pending_delta. + entry.credit -= pending_delta*entry.size; + const float credit_density = entry.credit_density(); + debug_assert(credit_density > 0.0f); + pending_delta += credit_density; + + Parent::remove_(least_valuable_it); + } + +private: + typedef Landlord_Naive Parent; + + // sort iterators by credit_density of the Entry they reference. + struct CD_greater + { + bool operator()(MapIt it1, MapIt it2) const + { + return Map::entry_from_it(it1).credit_density() > + Map::entry_from_it(it2).credit_density(); + } + }; + // wrapper on top of priority_queue that allows 'heap re-sift' + // (see on_access). + // notes: + // - greater comparator makes pri_q.top() the one with + // LEAST credit_density, which is what we want. + // - deriving from an STL container is a bit dirty, but we need this + // to get at the underlying data (priority_queue interface is not + // very capable). + class PriQ: public std::priority_queue, CD_greater> + { + public: + void ensure_heap_order() + { + // TODO: this is actually N*logN - ouch! that explains high + // CPU cost in profile. this is called after only 1 item has + // changed, so a logN "sift" operation ought to suffice. + // that's not supported by the STL heap functions, so we'd + // need a better implementation. pending.. + std::make_heap(this->c.begin(), this->c.end(), this->comp); + } + }; + PriQ pri_q; + + // delta values that have accumulated over several + // remove_least_valuable() calls. applied during add(). + float pending_delta; + + void commit_pending_delta() + { + if(pending_delta > 0.0f) + { + this->charge_all(pending_delta); + pending_delta = 0.0f; + + // we've changed entry credit, so the heap order *may* have been + // violated; reorder the pri queue. (I don't think so, + // due to definition of delta, but we'll play it safe) + pri_q.ensure_heap_order(); + } + } +}; + + +// +// functor that implements division of first arg by second +// + +// this is used to calculate credit_density(); performance matters +// because this is called for each entry during each remove operation. + +// floating-point division (fairly slow) +class Divider_Naive +{ +public: + Divider_Naive() {} // needed for default CacheEntry ctor + Divider_Naive(float UNUSED(x)) {} + float operator()(float val, float divisor) const + { + return val / divisor; + } +}; + +// caches reciprocal of divisor and multiplies by that. +// tradeoff: only 4 clocks (instead of 20), but 4 bytes extra per entry. +class Divider_Recip +{ + float recip; +public: + Divider_Recip() {} // needed for default CacheEntry ctor + Divider_Recip(float x) { recip = 1.0f / x; } + float operator()(float val, float UNUSED(divisor)) const + { + return val * recip; + } +}; + +// TODO: use SSE/3DNow RCP instruction? not yet, because not all systems +// support it and overhead of detecting this support eats into any gains. + +// initial implementation for testing purposes; quite inefficient. +template +class LRU +{ +public: + bool empty() const + { + return lru.empty(); + } + + void add(Key key, const Entry& entry) + { + lru.push_back(KeyAndEntry(key, entry)); + } + + bool find(Key key, const Entry** pentry) const + { + CIt it = std::find_if(lru.begin(), lru.end(), KeyEq(key)); + if(it == lru.end()) + return false; + *pentry = &it->entry; + return true; + } + + void remove(Key key) + { + std::remove_if(lru.begin(), lru.end(), KeyEq(key)); + } + + void on_access(Entry& entry) + { + for(It it = lru.begin(); it != lru.end(); ++it) + { + if(&entry == &it->entry) + { + add(it->key, it->entry); + lru.erase(it); + return; + } + } + debug_warn("entry not found in list"); + } + + void remove_least_valuable(std::list& entry_list) + { + entry_list.push_back(lru.front().entry); + lru.pop_front(); + } + +private: + struct KeyAndEntry + { + Key key; + Entry entry; + KeyAndEntry(Key key_, const Entry& entry_) + : key(key_), entry(entry_) {} + }; + class KeyEq + { + Key key; + public: + KeyEq(Key key_) : key(key_) {} + bool operator()(const KeyAndEntry& ke) const + { + return ke.key == key; + } + }; + + typedef std::list List; + typedef typename List::iterator It; + typedef typename List::const_iterator CIt; + List lru; +}; + + +// +// Cache +// + +template +< +typename Key, typename Item, +// see documentation above for Manager's interface. +template class Manager = Landlord_Cached, +class Divider = Divider_Naive +> +class Cache +{ +public: + Cache() : mgr() {} + + void add(Key key, Item item, size_t size, uint cost) + { + return mgr.add(key, Entry(item, size, cost)); + } + + // remove the entry identified by . expected usage is to check + // if present and determine size via retrieve(), so no need for + // error checking. + // useful for invalidating single cache entries. + void remove(Key key) + { + mgr.remove(key); + } + + // if there is no entry for in the cache, return false. + // otherwise, return true and pass back item and (optionally) size. + // + // if refill_credit (default), the cache manager 'rewards' this entry, + // tending to keep it in cache longer. this parameter is not used in + // normal operation - it's only for special cases where we need to + // make an end run around the cache accounting (e.g. for cache simulator). + bool retrieve(Key key, Item& item, size_t* psize = 0, bool refill_credit = true) + { + const Entry* entry; + if(!mgr.find(key, &entry)) + return false; + + item = entry->item; + if(psize) + *psize = entry->size; + + if(refill_credit) + mgr.on_access((Entry&)*entry); + + return true; + } + + // toss out the least valuable entry. return false if cache is empty, + // otherwise true and (optionally) pass back its item and size. + bool remove_least_valuable(Item* pItem = 0, size_t* pSize = 0) + { + // as an artefact of the cache eviction policy, several entries + // may be "shaken loose" by one call to remove_least_valuable. + // we cache them in a list to disburden callers (they always get + // exactly one). + if(entries_awaiting_eviction.empty()) + { + if(empty()) + return false; + + mgr.remove_least_valuable(entries_awaiting_eviction); + debug_assert(!entries_awaiting_eviction.empty()); + } + + const Entry& entry = entries_awaiting_eviction.front(); + if(pItem) + *pItem = entry.item; + if(pSize) + *pSize = entry.size; + entries_awaiting_eviction.pop_front(); + + return true; + } + + bool empty() const + { + return mgr.empty(); + } + +private: + // this is applicable to all cache management policies and stores all + // required information. a Divider functor is used to implement + // division for credit_density. + template struct CacheEntry + { + Item item; + size_t size; + uint cost; + float credit; + + InnerDivider divider; + + // needed for mgr.remove_least_valuable's entry_copy + CacheEntry() {} + + CacheEntry(Item item_, size_t size_, uint cost_) + : item(item_), divider((float)size_) + { + size = size_; + cost = cost_; + credit = cost; + + // else divider will fail + debug_assert(size != 0); + } + + float credit_density() const + { + return divider(credit, (float)size); + } + }; + typedef CacheEntry Entry; + + // see note in remove_least_valuable(). + std::list entries_awaiting_eviction; + + Manager mgr; +}; + +#endif // #ifndef CACHE_ADT_H__ diff --git a/source/lib/debug.cpp b/source/lib/debug.cpp index d9fa183ecf..50defba521 100644 --- a/source/lib/debug.cpp +++ b/source/lib/debug.cpp @@ -484,13 +484,29 @@ static const wchar_t* build_error_message(wchar_t* buf, size_t max_chars, if(!buf) return L"(insufficient memory to generate error message)"; + char errno_description[100] = {'?'}; + LibError errno_equiv = LibError_from_errno(false); + if(errno_equiv != ERR_FAIL) // meaningful translation + error_description_r(errno_equiv, errno_description, ARRAY_SIZE(errno_description)); + + char os_error[100]; + if(sys_error_description_r(0, os_error, ARRAY_SIZE(os_error)) != INFO_OK) + strcpy_s(os_error, ARRAY_SIZE(os_error), "?"); + static const wchar_t fmt[] = L"%ls\r\n" L"Location: %hs:%d (%hs)\r\n" + L"errno = %d (%hs)\r\n" + L"OS error = %hs\r\n" L"\r\n" L"Call stack:\r\n" L"\r\n"; - int len = swprintf(buf,max_chars,fmt, description, fn_only, line, func); + int len = swprintf(buf,max_chars,fmt, + description, + fn_only, line, func, + errno, errno_description, + os_error + ); if(len < 0) return L"(error while formatting error message)"; diff --git a/source/lib/lib_errors.h b/source/lib/lib_errors.h index 7e0964a639..d83f5ff88e 100644 --- a/source/lib/lib_errors.h +++ b/source/lib/lib_errors.h @@ -457,18 +457,18 @@ ERR(-100800, ERR_SYM_NO_STACK_FRAMES_FOUND, "No stack frames found") ERR(-100801, ERR_SYM_UNRETRIEVABLE_STATIC, "Value unretrievable (stored in external module)") ERR(-100802, ERR_SYM_UNRETRIEVABLE_REG, "Value unretrievable (stored in register)") ERR(-100803, ERR_SYM_TYPE_INFO_UNAVAILABLE, "Error getting type_info") +ERR(-100804, ERR_SYM_INTERNAL_ERROR, "Exception raised while processing a symbol") +ERR(-100805, ERR_SYM_UNSUPPORTED, "Symbol type not (fully) supported") +ERR(-100806, ERR_SYM_CHILD_NOT_FOUND, "Symbol does not have the given child") // .. this limit is to prevent infinite recursion. -ERR(-100804, ERR_SYM_NESTING_LIMIT, "Symbol nesting too deep or infinite recursion") +ERR(-100807, ERR_SYM_NESTING_LIMIT, "Symbol nesting too deep or infinite recursion") // .. this limit is to prevent large symbols (e.g. arrays or linked lists) from // hogging all stack trace buffer space. -ERR(-100805, ERR_SYM_SINGLE_SYMBOL_LIMIT, "Symbol has produced too much output") -ERR(-100806, ERR_SYM_INTERNAL_ERROR, "Exception raised while processing a symbol") -ERR(-100807, ERR_SYM_UNSUPPORTED, "Symbol type not (fully) supported") +ERR(-100808, ERR_SYM_SINGLE_SYMBOL_LIMIT, "Symbol has produced too much output") // .. one of the dump_sym* functions decided not to output anything at // all (e.g. for member functions in UDTs - we don't want those). // therefore, skip any post-symbol formatting (e.g. ",") as well. -ERR(-100808, ERR_SYM_SUPPRESS_OUTPUT, "Symbol was suppressed") -ERR(-100809, ERR_SYM_CHILD_NOT_FOUND, "Symbol does not have the given child") +ERR(+100809, INFO_SYM_SUPPRESS_OUTPUT, "Symbol was suppressed") // STL debug ERR(-100900, ERR_STL_CNT_UNKNOWN, "Unknown STL container type_name") diff --git a/source/lib/ogl.h b/source/lib/ogl.h index 5354b50165..2493e18cea 100644 --- a/source/lib/ogl.h +++ b/source/lib/ogl.h @@ -71,61 +71,6 @@ extern "C" { #endif -// Enums from EXT_framebuffer_object -#ifndef GL_FRAMEBUFFER_EXT -#define GL_FRAMEBUFFER_EXT 0x8D40 -#define GL_RENDERBUFFER_EXT 0x8D41 -#define GL_STENCIL_INDEX1_EXT 0x8D46 -#define GL_STENCIL_INDEX4_EXT 0x8D47 -#define GL_STENCIL_INDEX8_EXT 0x8D48 -#define GL_STENCIL_INDEX16_EXT 0x8D49 -#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 -#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 -#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 -#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 -#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 -#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 -#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 -#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 -#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 -#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 -#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 -#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 -#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 -#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 -#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 -#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 -#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 -#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 -#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 -#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 -#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 -#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 -#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 -#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 -#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA -#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB -#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC -#define GL_COLOR_ATTACHMENT13_EXT 0x8CED -#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE -#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF -#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 -#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 -#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 -#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 -#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 -#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 -#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA -#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB -#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC -#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD -#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 -#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 -#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF -#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 -#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 -#endif - // // extensions // diff --git a/source/lib/res/file/file_cache.cpp b/source/lib/res/file/file_cache.cpp index 7a930e3132..9c1367d352 100644 --- a/source/lib/res/file/file_cache.cpp +++ b/source/lib/res/file/file_cache.cpp @@ -27,7 +27,7 @@ #include "lib/allocators.h" #include "lib/byte_order.h" -#include "lib/adts.h" +#include "lib/cache_adt.h" #include "file_internal.h" //----------------------------------------------------------------------------- diff --git a/source/lib/sysdep/sysdep.h b/source/lib/sysdep/sysdep.h index 25d1305712..db3bd2d563 100644 --- a/source/lib/sysdep/sysdep.h +++ b/source/lib/sysdep/sysdep.h @@ -233,9 +233,12 @@ extern LibError sys_cursor_free(void* cursor); // misc // -// OS-specific backend for error_description_r. -// NB: it is expected to be rare that OS return/error codes are actually -// seen by user code, but we still translate them for completeness. +// describe the current OS error state. +// +// err: if not 0, use that as the error code to translate; otherwise, +// uses GetLastError or similar. +// rationale: it is expected to be rare that OS return/error codes are +// actually seen by user code, but we leave the possibility open. extern LibError sys_error_description_r(int err, char* buf, size_t max_chars); // determine filename of the module to whom the given address belongs. diff --git a/source/lib/sysdep/win/SDL_keysym.h b/source/lib/sysdep/win/SDL_keysym.h deleted file mode 100644 index 4429571c27..0000000000 --- a/source/lib/sysdep/win/SDL_keysym.h +++ /dev/null @@ -1,316 +0,0 @@ -/* - SDL - Simple DirectMedia Layer - Copyright (C) 1997-2004 Sam Lantinga - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - Sam Lantinga - slouken@libsdl.org -*/ - -#ifdef SAVE_RCSID -static char rcsid = - "@(#) $Id$"; -#endif - -#ifndef _SDL_keysym_h -#define _SDL_keysym_h - -/* What we really want is a mapping of every raw key on the keyboard. - To support international keyboards, we use the range 0xA1 - 0xFF - as international virtual keycodes. We'll follow in the footsteps of X11... - The names of the keys - */ - -typedef enum { - /* The keyboard syms have been cleverly chosen to map to ASCII */ - SDLK_UNKNOWN = 0, - SDLK_FIRST = 0, - SDLK_BACKSPACE = 8, - SDLK_TAB = 9, - SDLK_CLEAR = 12, - SDLK_RETURN = 13, - SDLK_PAUSE = 19, - SDLK_ESCAPE = 27, - SDLK_SPACE = 32, - SDLK_EXCLAIM = 33, - SDLK_QUOTEDBL = 34, - SDLK_HASH = 35, - SDLK_DOLLAR = 36, - SDLK_AMPERSAND = 38, - SDLK_QUOTE = 39, - SDLK_LEFTPAREN = 40, - SDLK_RIGHTPAREN = 41, - SDLK_ASTERISK = 42, - SDLK_PLUS = 43, - SDLK_COMMA = 44, - SDLK_MINUS = 45, - SDLK_PERIOD = 46, - SDLK_SLASH = 47, - SDLK_0 = 48, - SDLK_1 = 49, - SDLK_2 = 50, - SDLK_3 = 51, - SDLK_4 = 52, - SDLK_5 = 53, - SDLK_6 = 54, - SDLK_7 = 55, - SDLK_8 = 56, - SDLK_9 = 57, - SDLK_COLON = 58, - SDLK_SEMICOLON = 59, - SDLK_LESS = 60, - SDLK_EQUALS = 61, - SDLK_GREATER = 62, - SDLK_QUESTION = 63, - SDLK_AT = 64, - /* - Skip uppercase letters - */ - SDLK_LEFTBRACKET = 91, - SDLK_BACKSLASH = 92, - SDLK_RIGHTBRACKET = 93, - SDLK_CARET = 94, - SDLK_UNDERSCORE = 95, - SDLK_BACKQUOTE = 96, - SDLK_a = 97, - SDLK_b = 98, - SDLK_c = 99, - SDLK_d = 100, - SDLK_e = 101, - SDLK_f = 102, - SDLK_g = 103, - SDLK_h = 104, - SDLK_i = 105, - SDLK_j = 106, - SDLK_k = 107, - SDLK_l = 108, - SDLK_m = 109, - SDLK_n = 110, - SDLK_o = 111, - SDLK_p = 112, - SDLK_q = 113, - SDLK_r = 114, - SDLK_s = 115, - SDLK_t = 116, - SDLK_u = 117, - SDLK_v = 118, - SDLK_w = 119, - SDLK_x = 120, - SDLK_y = 121, - SDLK_z = 122, - SDLK_DELETE = 127, - /* End of ASCII mapped keysyms */ - - /* International keyboard syms */ - SDLK_WORLD_0 = 160, /* 0xA0 */ - SDLK_WORLD_1 = 161, - SDLK_WORLD_2 = 162, - SDLK_WORLD_3 = 163, - SDLK_WORLD_4 = 164, - SDLK_WORLD_5 = 165, - SDLK_WORLD_6 = 166, - SDLK_WORLD_7 = 167, - SDLK_WORLD_8 = 168, - SDLK_WORLD_9 = 169, - SDLK_WORLD_10 = 170, - SDLK_WORLD_11 = 171, - SDLK_WORLD_12 = 172, - SDLK_WORLD_13 = 173, - SDLK_WORLD_14 = 174, - SDLK_WORLD_15 = 175, - SDLK_WORLD_16 = 176, - SDLK_WORLD_17 = 177, - SDLK_WORLD_18 = 178, - SDLK_WORLD_19 = 179, - SDLK_WORLD_20 = 180, - SDLK_WORLD_21 = 181, - SDLK_WORLD_22 = 182, - SDLK_WORLD_23 = 183, - SDLK_WORLD_24 = 184, - SDLK_WORLD_25 = 185, - SDLK_WORLD_26 = 186, - SDLK_WORLD_27 = 187, - SDLK_WORLD_28 = 188, - SDLK_WORLD_29 = 189, - SDLK_WORLD_30 = 190, - SDLK_WORLD_31 = 191, - SDLK_WORLD_32 = 192, - SDLK_WORLD_33 = 193, - SDLK_WORLD_34 = 194, - SDLK_WORLD_35 = 195, - SDLK_WORLD_36 = 196, - SDLK_WORLD_37 = 197, - SDLK_WORLD_38 = 198, - SDLK_WORLD_39 = 199, - SDLK_WORLD_40 = 200, - SDLK_WORLD_41 = 201, - SDLK_WORLD_42 = 202, - SDLK_WORLD_43 = 203, - SDLK_WORLD_44 = 204, - SDLK_WORLD_45 = 205, - SDLK_WORLD_46 = 206, - SDLK_WORLD_47 = 207, - SDLK_WORLD_48 = 208, - SDLK_WORLD_49 = 209, - SDLK_WORLD_50 = 210, - SDLK_WORLD_51 = 211, - SDLK_WORLD_52 = 212, - SDLK_WORLD_53 = 213, - SDLK_WORLD_54 = 214, - SDLK_WORLD_55 = 215, - SDLK_WORLD_56 = 216, - SDLK_WORLD_57 = 217, - SDLK_WORLD_58 = 218, - SDLK_WORLD_59 = 219, - SDLK_WORLD_60 = 220, - SDLK_WORLD_61 = 221, - SDLK_WORLD_62 = 222, - SDLK_WORLD_63 = 223, - SDLK_WORLD_64 = 224, - SDLK_WORLD_65 = 225, - SDLK_WORLD_66 = 226, - SDLK_WORLD_67 = 227, - SDLK_WORLD_68 = 228, - SDLK_WORLD_69 = 229, - SDLK_WORLD_70 = 230, - SDLK_WORLD_71 = 231, - SDLK_WORLD_72 = 232, - SDLK_WORLD_73 = 233, - SDLK_WORLD_74 = 234, - SDLK_WORLD_75 = 235, - SDLK_WORLD_76 = 236, - SDLK_WORLD_77 = 237, - SDLK_WORLD_78 = 238, - SDLK_WORLD_79 = 239, - SDLK_WORLD_80 = 240, - SDLK_WORLD_81 = 241, - SDLK_WORLD_82 = 242, - SDLK_WORLD_83 = 243, - SDLK_WORLD_84 = 244, - SDLK_WORLD_85 = 245, - SDLK_WORLD_86 = 246, - SDLK_WORLD_87 = 247, - SDLK_WORLD_88 = 248, - SDLK_WORLD_89 = 249, - SDLK_WORLD_90 = 250, - SDLK_WORLD_91 = 251, - SDLK_WORLD_92 = 252, - SDLK_WORLD_93 = 253, - SDLK_WORLD_94 = 254, - SDLK_WORLD_95 = 255, /* 0xFF */ - - /* Numeric keypad */ - SDLK_KP0 = 256, - SDLK_KP1 = 257, - SDLK_KP2 = 258, - SDLK_KP3 = 259, - SDLK_KP4 = 260, - SDLK_KP5 = 261, - SDLK_KP6 = 262, - SDLK_KP7 = 263, - SDLK_KP8 = 264, - SDLK_KP9 = 265, - SDLK_KP_PERIOD = 266, - SDLK_KP_DIVIDE = 267, - SDLK_KP_MULTIPLY = 268, - SDLK_KP_MINUS = 269, - SDLK_KP_PLUS = 270, - SDLK_KP_ENTER = 271, - SDLK_KP_EQUALS = 272, - - /* Arrows + Home/End pad */ - SDLK_UP = 273, - SDLK_DOWN = 274, - SDLK_RIGHT = 275, - SDLK_LEFT = 276, - SDLK_INSERT = 277, - SDLK_HOME = 278, - SDLK_END = 279, - SDLK_PAGEUP = 280, - SDLK_PAGEDOWN = 281, - - /* Function keys */ - SDLK_F1 = 282, - SDLK_F2 = 283, - SDLK_F3 = 284, - SDLK_F4 = 285, - SDLK_F5 = 286, - SDLK_F6 = 287, - SDLK_F7 = 288, - SDLK_F8 = 289, - SDLK_F9 = 290, - SDLK_F10 = 291, - SDLK_F11 = 292, - SDLK_F12 = 293, - SDLK_F13 = 294, - SDLK_F14 = 295, - SDLK_F15 = 296, - - /* Key state modifier keys */ - SDLK_NUMLOCK = 300, - SDLK_CAPSLOCK = 301, - SDLK_SCROLLOCK = 302, - SDLK_RSHIFT = 303, - SDLK_LSHIFT = 304, - SDLK_RCTRL = 305, - SDLK_LCTRL = 306, - SDLK_RALT = 307, - SDLK_LALT = 308, - SDLK_RMETA = 309, - SDLK_LMETA = 310, - SDLK_LSUPER = 311, /* Left "Windows" key */ - SDLK_RSUPER = 312, /* Right "Windows" key */ - SDLK_MODE = 313, /* "Alt Gr" key */ - SDLK_COMPOSE = 314, /* Multi-key compose key */ - - /* Miscellaneous function keys */ - SDLK_HELP = 315, - SDLK_PRINT = 316, - SDLK_SYSREQ = 317, - SDLK_BREAK = 318, - SDLK_MENU = 319, - SDLK_POWER = 320, /* Power Macintosh power key */ - SDLK_EURO = 321, /* Some european keyboards */ - SDLK_UNDO = 322, /* Atari keyboard has Undo */ - - /* Add any other keys here */ - - SDLK_LAST -} SDLKey; - -/* Enumeration of valid key mods (possibly OR'd together) */ -typedef enum { - KMOD_NONE = 0x0000, - KMOD_LSHIFT= 0x0001, - KMOD_RSHIFT= 0x0002, - KMOD_LCTRL = 0x0040, - KMOD_RCTRL = 0x0080, - KMOD_LALT = 0x0100, - KMOD_RALT = 0x0200, - KMOD_LMETA = 0x0400, - KMOD_RMETA = 0x0800, - KMOD_NUM = 0x1000, - KMOD_CAPS = 0x2000, - KMOD_MODE = 0x4000, - KMOD_RESERVED = 0x8000 -} SDLMod; - -#define KMOD_CTRL (KMOD_LCTRL|KMOD_RCTRL) -#define KMOD_SHIFT (KMOD_LSHIFT|KMOD_RSHIFT) -#define KMOD_ALT (KMOD_LALT|KMOD_RALT) -#define KMOD_META (KMOD_LMETA|KMOD_RMETA) - -#endif /* _SDL_keysym_h */ diff --git a/source/lib/sysdep/win/SDL_vkeys.h b/source/lib/sysdep/win/SDL_vkeys.h deleted file mode 100644 index 4417ad66bb..0000000000 --- a/source/lib/sysdep/win/SDL_vkeys.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - SDL - Simple DirectMedia Layer - Copyright (C) 1997-2004 Sam Lantinga - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public - License along with this library; if not, write to the Free - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - Sam Lantinga - slouken@libsdl.org -*/ - -#ifdef SAVE_RCSID -static char rcsid = - "@(#) $Id$"; -#endif - -#ifndef VK_0 -#define VK_0 '0' -#define VK_1 '1' -#define VK_2 '2' -#define VK_3 '3' -#define VK_4 '4' -#define VK_5 '5' -#define VK_6 '6' -#define VK_7 '7' -#define VK_8 '8' -#define VK_9 '9' -#define VK_A 'A' -#define VK_B 'B' -#define VK_C 'C' -#define VK_D 'D' -#define VK_E 'E' -#define VK_F 'F' -#define VK_G 'G' -#define VK_H 'H' -#define VK_I 'I' -#define VK_J 'J' -#define VK_K 'K' -#define VK_L 'L' -#define VK_M 'M' -#define VK_N 'N' -#define VK_O 'O' -#define VK_P 'P' -#define VK_Q 'Q' -#define VK_R 'R' -#define VK_S 'S' -#define VK_T 'T' -#define VK_U 'U' -#define VK_V 'V' -#define VK_W 'W' -#define VK_X 'X' -#define VK_Y 'Y' -#define VK_Z 'Z' -#endif /* VK_0 */ - -#ifndef VK_OEM_1 -#define VK_OEM_1 0xba -#define VK_OEM_2 0xbf -#define VK_OEM_3 0xc0 -#define VK_OEM_4 0xdb -#define VK_OEM_5 0xdc -#define VK_OEM_6 0xdd -#define VK_OEM_7 0xde -#define VK_OEM_8 0xdf -#define VK_OEM_PLUS 0xbb -#define VK_OEM_COMMA 0xbc -#define VK_OEM_MINUS 0xbd -#define VK_OEM_PERIOD 0xbe -#endif - diff --git a/source/lib/sysdep/win/wdbg_sym.cpp b/source/lib/sysdep/win/wdbg_sym.cpp index d530994e5f..145a853e39 100644 --- a/source/lib/sysdep/win/wdbg_sym.cpp +++ b/source/lib/sysdep/win/wdbg_sym.cpp @@ -47,7 +47,8 @@ #endif -// automatic module shutdown (before process termination) +#pragma data_seg(WIN_CALLBACK_PRE_LIBC(b)) +WIN_REGISTER_FUNC(wdbg_sym_init); #pragma data_seg(WIN_CALLBACK_POST_ATEXIT(b)) WIN_REGISTER_FUNC(wdbg_sym_shutdown); #pragma data_seg() @@ -225,6 +226,7 @@ LibError debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, // stack walk //---------------------------------------------------------------------------- +static VOID (*pRtlCaptureContext)(PCONTEXT*); /* Subroutine linkage example code: @@ -354,12 +356,7 @@ static LibError walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip #if CPU_IA32 ia32_get_current_context(&context); #else - // try to import RtlCaptureContext (available on WinXP and later) - // .. note: kernel32 is always loaded into every process, so we - // don't need LoadLibrary/FreeLibrary. - HMODULE hKernel32Dll = GetModuleHandle("kernel32.dll"); - VOID (*pRtlCaptureContext)(PCONTEXT*); - *(void**)&pRtlCaptureContext = GetProcAddress(hKernel32Dll, "RtlCaptureContext"); + // preferred implementation (was imported during module init) if(pRtlCaptureContext) pRtlCaptureContext(&context); // not available: raise+handle an exception; grab the reported context. @@ -524,7 +521,7 @@ static wchar_t* out_pos; // new position exceeds the limit and aborts if so. // slight wrinkle: since we don't want each level of UDTs to successively // realize the limit has been hit and display the error message, we -// return ERR_SYM_SINGLE_SYMBOL_LIMIT once and thereafter ERR_SYM_SUPPRESS_OUTPUT. +// return ERR_SYM_SINGLE_SYMBOL_LIMIT once and thereafter INFO_SYM_SUPPRESS_OUTPUT. // // * example: local variables, as opposed to child symbols in a UDT. static wchar_t* out_latched_pos; @@ -609,7 +606,7 @@ static void out_latch_pos() static LibError out_check_limit() { if(out_have_warned_of_limit) - return ERR_SYM_SUPPRESS_OUTPUT; // NOWARN + return INFO_SYM_SUPPRESS_OUTPUT; if(out_pos - out_latched_pos > 3000) // ~30 lines { out_have_warned_of_limit = true; @@ -736,7 +733,7 @@ static void dump_error(LibError err, const u8* p) case ERR_SYM_INTERNAL_ERROR: out(L"(unavailable - internal error)\r\n"); break; - case ERR_SYM_SUPPRESS_OUTPUT: + case INFO_SYM_SUPPRESS_OUTPUT: // not an error; do not output anything. handled by caller. break; default: @@ -840,7 +837,7 @@ static LibError dump_sequence(DebugIterator el_iterator, void* internal, // there was no output for this child; undo its indentation (if any), // skip everything below and proceed with the next child. - if(err == ERR_SYM_SUPPRESS_OUTPUT) + if(err == INFO_SYM_SUPPRESS_OUTPUT) { if(!fits_on_one_line) UNINDENT; @@ -1276,7 +1273,7 @@ static LibError dump_sym_enum(DWORD type_id, const u8* p, DumpState UNUSED(state static LibError dump_sym_function(DWORD UNUSED(type_id), const u8* UNUSED(p), DumpState UNUSED(state)) { - return ERR_SYM_SUPPRESS_OUTPUT; // NOWARN + return INFO_SYM_SUPPRESS_OUTPUT; } @@ -1624,7 +1621,7 @@ static LibError udt_dump_normal(const wchar_t* type_name, const u8* p, size_t si // there was no output for this child; undo its indentation (if any), // skip everything below and proceed with the next child. - if(err == ERR_SYM_SUPPRESS_OUTPUT) + if(err == INFO_SYM_SUPPRESS_OUTPUT) { if(!fits_on_one_line) UNINDENT; @@ -1710,7 +1707,7 @@ done: static LibError dump_sym_vtable(DWORD UNUSED(type_id), const u8* UNUSED(p), DumpState UNUSED(state)) { // unsupported (vtable internals are undocumented; too much work). - return ERR_SYM_SUPPRESS_OUTPUT; // NOWARN + return INFO_SYM_SUPPRESS_OUTPUT; } @@ -1790,7 +1787,7 @@ static BOOL CALLBACK dump_sym_cb(SYMBOL_INFO* sym, ULONG UNUSED(size), void* UNU INDENT; LibError err = dump_sym(sym->Index, p, state); dump_error(err, p); - if(err == ERR_SYM_SUPPRESS_OUTPUT) + if(err == INFO_SYM_SUPPRESS_OUTPUT) UNINDENT; else out(L"\r\n"); @@ -1932,6 +1929,20 @@ fail: +static LibError wdbg_sym_init() +{ + // try to import RtlCaptureContext (available on WinXP and later). + // it's used in walk_stack; import here to avoid overhead of doing so + // on every call. if not available, walk_stack emulates it. + // + // note: kernel32 is always loaded into every process, so we + // don't need LoadLibrary/FreeLibrary. + HMODULE hKernel32Dll = GetModuleHandle("kernel32.dll"); + *(void**)&pRtlCaptureContext = GetProcAddress(hKernel32Dll, "RtlCaptureContext"); + + return INFO_OK; +} + static LibError wdbg_sym_shutdown() { diff --git a/source/lib/sysdep/win/wsdl.cpp b/source/lib/sysdep/win/wsdl.cpp index 7d88f15f31..5273d4981a 100644 --- a/source/lib/sysdep/win/wsdl.cpp +++ b/source/lib/sysdep/win/wsdl.cpp @@ -35,7 +35,6 @@ #include "lib/lib.h" #include "lib/posix.h" #include "lib/ogl.h" // needed to pull in the delay-loaded opengl32.dll -#include "SDL_vkeys.h" // for easy removal of DirectDraw dependency (used to query total video mem) @@ -574,6 +573,10 @@ static void queue_key_event(uint type, uint sdlk, WCHAR unicode_char) static Uint8 keys[SDLK_LAST]; +// winuser.h promises VK_0 and VK_A etc. match ASCII value. +#define VK_0 '0' +#define VK_A 'A' + static void init_vkmap(SDLKey (&VK_keymap)[256]) { int i; diff --git a/source/lib/sysdep/win/wsdl.h b/source/lib/sysdep/win/wsdl.h index 41bb672dbd..e126bbfa59 100644 --- a/source/lib/sysdep/win/wsdl.h +++ b/source/lib/sysdep/win/wsdl.h @@ -24,7 +24,7 @@ #define WSDL_H__ #include "lib/types.h" -#include "SDL_keysym.h" +#include "SDL/SDL_keysym.h" #ifdef __cplusplus extern "C" { diff --git a/source/lib/sysdep/win/wsysdep.cpp b/source/lib/sysdep/win/wsysdep.cpp index 7efd92d55b..91e4bd8166 100644 --- a/source/lib/sysdep/win/wsysdep.cpp +++ b/source/lib/sysdep/win/wsysdep.cpp @@ -547,19 +547,20 @@ LibError sys_cursor_free(void* cursor) // misc //----------------------------------------------------------------------------- -// OS-specific backend for error_description_r. -// NB: it is expected to be rare that OS return/error codes are actually -// seen by user code, but we still translate them for completeness. -LibError sys_error_description_r(int err, char* buf, size_t max_chars) +LibError sys_error_description_r(int user_err, char* buf, size_t max_chars) { + DWORD err = (DWORD)user_err; // not in our range (Win32 error numbers are positive) - if(err < 0) + if(user_err < 0) return ERR_FAIL; // NOWARN + // user doesn't know error code; get current error state + if(!user_err) + err = GetLastError(); const LPCVOID source = 0; // ignored (we're not using FROM_HMODULE etc.) const DWORD lang_id = 0; // look for neutral, then current locale va_list* args = 0; // we don't care about "inserts" - DWORD chars_output = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, source, (DWORD)err, + DWORD chars_output = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, source, err, lang_id, buf, (DWORD)max_chars, args); if(!chars_output) WARN_RETURN(ERR_FAIL); diff --git a/source/lib/tests/test_adts.h b/source/lib/tests/test_adts.h index 1681605fec..74ba8d7e09 100644 --- a/source/lib/tests/test_adts.h +++ b/source/lib/tests/test_adts.h @@ -73,108 +73,3 @@ public: TS_ASSERT(equal(begin, end, deq.begin())); } }; - -class TestCache: public CxxTest::TestSuite -{ -public: - void test_cache_perf() - { - 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 - } - - // ensures all 3 variants of Landlord<> behave the same - void test_cache_policies() - { - Cache c1; - Cache c2; - Cache c3; - - 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); - TS_ASSERT_EQUALS(removed1, removed2); - TS_ASSERT_EQUALS(removed2, removed3); - if (removed1) - { - TS_ASSERT_EQUALS(size1, size2); - TS_ASSERT_EQUALS(value1, value2); - TS_ASSERT_EQUALS(size2, size3); - TS_ASSERT_EQUALS(value2, value3); - } - } // else - } // for i - } -}; diff --git a/source/lib/tests/test_cache_adt.h b/source/lib/tests/test_cache_adt.h new file mode 100644 index 0000000000..5b5999a573 --- /dev/null +++ b/source/lib/tests/test_cache_adt.h @@ -0,0 +1,108 @@ +#include "lib/self_test.h" + +#include "lib/cache_adt.h" + +class TestCache: public CxxTest::TestSuite +{ +public: + void test_cache_perf() + { + 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 + } + + // ensures all 3 variants of Landlord<> behave the same + void test_cache_policies() + { + Cache c1; + Cache c2; + Cache c3; + + 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); + TS_ASSERT_EQUALS(removed1, removed2); + TS_ASSERT_EQUALS(removed2, removed3); + if (removed1) + { + TS_ASSERT_EQUALS(size1, size2); + TS_ASSERT_EQUALS(value1, value2); + TS_ASSERT_EQUALS(size2, size3); + TS_ASSERT_EQUALS(value2, value3); + } + } // else + } // for i + } +}; \ No newline at end of file diff --git a/source/lib/tests/test_string_s.h b/source/lib/tests/test_string_s.h index da7c30a931..b08c2501e0 100644 --- a/source/lib/tests/test_string_s.h +++ b/source/lib/tests/test_string_s.h @@ -30,8 +30,9 @@ class TestString_s : public CxxTest::TestSuite static void TEST_LEN(const char* string, size_t limit, + size_t expected_len) { - TS_ASSERT_EQUALS(strnlen((string), (limit)), (expected)); + TS_ASSERT_EQUALS(strnlen((string), (limit)), (expected_len)); } static void TEST_CPY(char* dst, size_t dst_max, const char* src, @@ -46,7 +47,7 @@ class TestString_s : public CxxTest::TestSuite static void TEST_CPY2(char* dst, size_t max_dst_chars, const char* src, int expected_ret, const char* expected_dst) { - int ret = strcpy_s((dst), ARRAY_SIZE(dst), (src)); + int ret = strcpy_s((dst), max_dst_chars, (src)); TS_ASSERT_EQUALS(ret, expected_ret); if(dst != 0) TS_ASSERT(!strcmp(dst, expected_dst)); @@ -62,7 +63,7 @@ class TestString_s : public CxxTest::TestSuite } static void TEST_CAT(char* dst, size_t max_dst_chars, const char* src, - int expected_ret, const char expected_dst) + int expected_ret, const char* expected_dst) { int ret = strcat_s(dst, max_dst_chars, src); TS_ASSERT_EQUALS(ret, expected_ret); @@ -186,7 +187,7 @@ public: void test_concatenate() { - TEST_CAT2(d3,3, s1, ,"1",0,"1a"); + TEST_CAT2(d3,3, s1, "1",0,"1a"); TEST_CAT2(d5,5, s1, "1",0,"1a"); TEST_CAT2(d6,6, s5, "",0,"abcde"); TEST_CAT2(d10,10, s5, "",0,"abcde"); diff --git a/source/lib/timer.h b/source/lib/timer.h index da789e4892..f7449dea19 100644 --- a/source/lib/timer.h +++ b/source/lib/timer.h @@ -49,6 +49,13 @@ extern float spf; // for time-since-last-frame use extern void calc_fps(void); +// +// cumulative timer API +// + +// this supplements in-game profiling by providing low-overhead, +// high resolution time accounting. + // 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 ia32_rdtsc) instead of @@ -73,15 +80,6 @@ typedef i64 TimerUnit; typedef double TimerUnit; #endif - -// -// cumulative timer API -// - -// this supplements in-game profiling by providing low-overhead, -// high resolution time accounting. - - // opaque - do not access its fields! // note: must be defined here because clients instantiate them; // fields cannot be made private due to C compatibility requirement. @@ -202,44 +200,60 @@ Example usage: #define TIMER_END(description) } -// used via TIMER_ACCRUE -class ScopeTimerAccrue +#if TIMER_USE_RAW_TICKS + +#if CPU_IA32 +// fast, not usable as wall-clock (http://www.gamedev.net/reference/programming/features/timing) +class TimerRdtsc { - TimerUnit t0; +public: + typedef i64 unit; + unit get_timestamp() const + { + return ia32_rdtsc(); + } +}; +#else +# error "port" +#endif // CPU_IA32 + +#else + +class TimerNormal +{ +public: + typedef double unit; + unit get_timestamp() const + { + return get_time(); + } +}; + +#endif // TIMER_USE_RAW_TICKS + + +// used via TIMER_ACCRUE +template class ScopeTimerAccrue +{ + TimerImpl impl; + typename TimerImpl::unit t0; TimerClient* tc; public: - ScopeTimerAccrue(TimerClient* tc_) + ScopeTimerAccrue(TimerClient* tc_) { -#if TIMER_USE_RAW_TICKS -# if CPU_IA32 - t0 = ia32_rdtsc(); -# else -# error "port" -# endif -#else - t0 = get_time(); -#endif + t0 = impl.get_timestamp(); tc = tc_; } - ~ScopeTimerAccrue() + ~ScopeTimerAccrue() { -#if TIMER_USE_RAW_TICKS -# if CPU_IA32 - TimerUnit t1 = ia32_rdtsc(); -# else -# error "port" -# endif -#else - TimerUnit t1 = get_time(); -#endif - TimerUnit dt = t1-t0; + TimerImpl::unit dt = impl.get_timestamp() - t0; timer_bill_client(tc, dt); } // disallow copying (makes no sense) private: - ScopeTimerAccrue& operator=(const ScopeTimerAccrue&); + ScopeTimerAccrue& operator=(const ScopeTimerAccrue&); }; @@ -272,6 +286,6 @@ Example usage: [at exit] timer_display_client_totals(); */ -#define TIMER_ACCRUE(client) ScopeTimerAccrue UID__(client) +#define TIMER_ACCRUE(client) ScopeTimerAccrue<> UID__(client) #endif // #ifndef TIMER_H diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 9cd4cabcda..10d0c6c428 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -5,6 +5,7 @@ #include "lib/ogl.h" #include "lib/timer.h" #include "lib/input.h" +#include "lib/app_hooks.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/gfx.h" #include "lib/res/res.h" @@ -915,6 +916,11 @@ void Init(int argc, char* argv[], uint flags) MICROLOG(L"init i18n"); I18n::LoadLanguage(NULL); + // override ah_translate with our i18n code. + AppHooks hooks = {0}; + hooks.translate = psTranslate; + set_app_hooks(&hooks); + // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in InitPs()) diff --git a/source/ps/Pyrogenesis.cpp b/source/ps/Pyrogenesis.cpp index 66a8089f27..a222975ff0 100644 --- a/source/ps/Pyrogenesis.cpp +++ b/source/ps/Pyrogenesis.cpp @@ -1,6 +1,33 @@ #include "precompiled.h" #include "Pyrogenesis.h" +#include "ps/i18n.h" DEFINE_ERROR(PS_OK, "OK"); DEFINE_ERROR(PS_FAIL, "Fail"); + +// overrides ah_translate. registered in GameSetup.cpp +const wchar_t* psTranslate(const wchar_t* text) +{ + // TODO: leaks memory returned by wcsdup + + // make sure i18n system is (already|still) initialized. + if(g_CurrentLocale) + { + // be prepared for this to fail, because translation potentially + // involves script code and the JS context might be corrupted. + try + { + CStrW ret = I18n::translate(text); + const wchar_t* text2 = wcsdup(ret.c_str()); + // only overwrite if wcsdup succeeded, i.e. not out of memory. + if(text2) + text = text2; + } + catch(...) + { + } + } + + return text; +} diff --git a/source/ps/Pyrogenesis.h b/source/ps/Pyrogenesis.h index cdff567fe3..6d421ccdad 100644 --- a/source/ps/Pyrogenesis.h +++ b/source/ps/Pyrogenesis.h @@ -22,4 +22,8 @@ DECLARE_ERROR(PS_FAIL); #define MICROLOG debug_wprintf_mem + +// overrides ah_translate. registered in GameSetup.cpp +extern const wchar_t* psTranslate(const wchar_t* text); + #endif