# 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.
This commit is contained in:
janwas 2006-06-23 17:41:55 +00:00
parent 2f065990f6
commit 7cb82ada2f
24 changed files with 1038 additions and 1552 deletions

View File

@ -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,
})

View File

@ -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"

View File

@ -23,15 +23,9 @@
#ifndef ADTS_H__
#define ADTS_H__
#include "lib.h"
#include <cfloat>
#pragma warning(push, 3) // VC7.1 STL not /W4 clean :(
#include <list>
#include <map>
#pragma warning(pop)
//-----------------------------------------------------------------------------
// dynamic (grow-able) hash table
//-----------------------------------------------------------------------------
template<typename Key, typename T> class DHT_Traits
{
@ -61,7 +55,6 @@ public:
};
// intended for pointer types
template<typename Key, typename T, typename Traits=DHT_Traits<Key,T> >
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<key, item> 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 <key> 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 <entry> 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>& 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<class Entries> 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 Entry, class Entries>
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 Entry, class Entries>
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<typename Key, typename Entry, template<class Entry, class Entries> 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>& 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 <it> 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<Key, Entry>
{
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<MapIt, bool> 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<Entry, Map> mcd_calc;
};
// Cache manger policies. (these are partial specializations of Landlord,
// adapting it to the template params required by Cache)
template<class Key, class Entry> class Landlord_Naive : public Landlord<Key, Entry, McdCalc_Naive> {};
template<class Key, class Entry> class Landlord_Cached: public Landlord<Key, Entry, McdCalc_Cached> {};
// 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<typename Key, class Entry>
class Landlord_Lazy : public Landlord_Naive<Key, Entry>
{
typedef typename Landlord_Naive<Key, Entry>::Map Map;
typedef typename Landlord_Naive<Key, Entry>::MapIt MapIt;
typedef typename Landlord_Naive<Key, Entry>::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>& 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<Key, Entry> 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<MapIt, std::vector<MapIt>, 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<typename Key, typename Entry>
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>& 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<KeyAndEntry> 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<typename Key, class Entry> 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 <key>. 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 <key> 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<class InnerDivider> 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<Divider> Entry;
// see note in remove_least_valuable().
std::list<Entry> entries_awaiting_eviction;
Manager<Key, Entry> mgr;
};
//
// FIFO bit queue
//
//-----------------------------------------------------------------------------
struct BitBuf
{
@ -981,9 +270,9 @@ struct BitBuf
};
//
//-----------------------------------------------------------------------------
// ring buffer - static array, accessible modulo n
//
//-----------------------------------------------------------------------------
template<class T, size_t n> 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 Entry> 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<Line> List;
typedef typename List::iterator List_iterator;
List lru_list;
typedef std::map<u64, List_iterator> 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__

View File

@ -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;
}

View File

@ -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 <text> if i18n functionality is available.
//
@ -105,10 +109,12 @@ FUNC(const wchar_t*, translate, (const wchar_t* text), (text), return)
// write <text> 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

731
source/lib/cache_adt.h Normal file
View File

@ -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 <cfloat>
#include <list>
#include <map>
/*
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<key, item> 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 <key> 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 <entry> 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>& 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<class Entries> 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 Entry, class Entries>
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 Entry, class Entries>
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<typename Key, typename Entry, template<class Entry, class Entries> 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>& 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 <it> 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<Key, Entry>
{
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<MapIt, bool> 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<Entry, Map> mcd_calc;
};
// Cache manger policies. (these are partial specializations of Landlord,
// adapting it to the template params required by Cache)
template<class Key, class Entry> class Landlord_Naive : public Landlord<Key, Entry, McdCalc_Naive> {};
template<class Key, class Entry> class Landlord_Cached: public Landlord<Key, Entry, McdCalc_Cached> {};
// 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<typename Key, class Entry>
class Landlord_Lazy : public Landlord_Naive<Key, Entry>
{
typedef typename Landlord_Naive<Key, Entry>::Map Map;
typedef typename Landlord_Naive<Key, Entry>::MapIt MapIt;
typedef typename Landlord_Naive<Key, Entry>::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>& 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<Key, Entry> 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<MapIt, std::vector<MapIt>, 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<typename Key, typename Entry>
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>& 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<KeyAndEntry> 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<typename Key, class Entry> 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 <key>. 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 <key> 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<class InnerDivider> 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<Divider> Entry;
// see note in remove_least_valuable().
std::list<Entry> entries_awaiting_eviction;
Manager<Key, Entry> mgr;
};
#endif // #ifndef CACHE_ADT_H__

View File

@ -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)";

View File

@ -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")

View File

@ -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
//

View File

@ -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"
//-----------------------------------------------------------------------------

View File

@ -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.

View File

@ -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 */

View File

@ -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

View File

@ -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()
{

View File

@ -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;

View File

@ -24,7 +24,7 @@
#define WSDL_H__
#include "lib/types.h"
#include "SDL_keysym.h"
#include "SDL/SDL_keysym.h"
#ifdef __cplusplus
extern "C" {

View File

@ -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);

View File

@ -73,108 +73,3 @@ public:
TS_ASSERT(equal(begin, end, deq.begin()));
}
};
class TestCache: public CxxTest::TestSuite
{
public:
void test_cache_perf()
{
Cache<int, int, Landlord_Naive> c1;
Cache<int, int, Landlord_Naive, Divider_Recip> c1r;
Cache<int, int, Landlord_Cached> c2;
Cache<int, int, Landlord_Cached, Divider_Recip> c2r;
Cache<int, int, Landlord_Lazy> c3;
Cache<int, int, Landlord_Lazy, Divider_Recip> 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<int, int, Landlord_Naive > c1;
Cache<int, int, Landlord_Cached> c2;
Cache<int, int, Landlord_Lazy > 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
}
};

View File

@ -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<int, int, Landlord_Naive> c1;
Cache<int, int, Landlord_Naive, Divider_Recip> c1r;
Cache<int, int, Landlord_Cached> c2;
Cache<int, int, Landlord_Cached, Divider_Recip> c2r;
Cache<int, int, Landlord_Lazy> c3;
Cache<int, int, Landlord_Lazy, Divider_Recip> 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<int, int, Landlord_Naive > c1;
Cache<int, int, Landlord_Cached> c2;
Cache<int, int, Landlord_Lazy > 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
}
};

View File

@ -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");

View File

@ -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 TimerImpl = TimerRdtsc> class ScopeTimerAccrue
{
TimerImpl impl;
typename TimerImpl::unit t0;
TimerClient* tc;
public:
ScopeTimerAccrue(TimerClient* tc_)
ScopeTimerAccrue<TimerImpl>(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<TimerImpl>()
{
#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<TimerImpl>& operator=(const ScopeTimerAccrue<TimerImpl>&);
};
@ -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

View File

@ -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())

View File

@ -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;
}

View File

@ -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