Index: ps/trunk/source/graphics/ObjectEntry.cpp =================================================================== --- ps/trunk/source/graphics/ObjectEntry.cpp (revision 16887) +++ ps/trunk/source/graphics/ObjectEntry.cpp (revision 16888) @@ -1,276 +1,275 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ObjectEntry.h" #include "graphics/Decal.h" #include "graphics/Material.h" #include "graphics/MaterialManager.h" #include "graphics/MeshManager.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ObjectBase.h" #include "graphics/ObjectManager.h" #include "graphics/ParticleManager.h" #include "graphics/SkeletonAnim.h" #include "graphics/TextureManager.h" #include "lib/rand.h" #include "ps/CLogger.h" #include "ps/Game.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "simulation2/Simulation2.h" #include CObjectEntry::CObjectEntry(CObjectBase* base, CSimulation2& simulation) : m_Base(base), m_Color(1.0f, 1.0f, 1.0f, 1.0f), m_Model(NULL), m_Outdated(false), m_Simulation(simulation) { } -template static void delete_pair_2nd(std::pair v) { delete v.second; } - CObjectEntry::~CObjectEntry() { - std::for_each(m_Animations.begin(), m_Animations.end(), delete_pair_2nd); + for (const std::pair& anim : m_Animations) + delete anim.second; delete m_Model; } bool CObjectEntry::BuildVariation(const std::vector >& selections, const std::vector& variationKey, CObjectManager& objectManager) { CObjectBase::Variation variation = m_Base->BuildVariation(variationKey); // Copy the chosen data onto this model: for (std::multimap::iterator it = variation.samplers.begin(); it != variation.samplers.end(); ++it) m_Samplers.push_back(it->second); m_ModelName = variation.model; if (! variation.color.empty()) { std::stringstream str; str << variation.color; int r, g, b; if (! (str >> r >> g >> b)) // Any trailing data is ignored LOGERROR("Actor '%s' has invalid RGB color '%s'", utf8_from_wstring(m_Base->m_ShortName), variation.color); else m_Color = CColor(r/255.0f, g/255.0f, b/255.0f, 1.0f); } if (variation.decal.m_SizeX && variation.decal.m_SizeZ) { CMaterial material = g_Renderer.GetMaterialManager().LoadMaterial(m_Base->m_Material); std::vector::iterator samp; for (samp = m_Samplers.begin(); samp != m_Samplers.end(); ++samp) { CTextureProperties textureProps(samp->m_SamplerFile); textureProps.SetWrap(GL_CLAMP_TO_BORDER); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); // TODO: Should check which renderpath is selected and only preload the necessary textures. texture->Prefetch(); material.AddSampler(CMaterial::TextureSampler(samp->m_SamplerName, texture)); } SDecal decal(material, variation.decal.m_SizeX, variation.decal.m_SizeZ, variation.decal.m_Angle, variation.decal.m_OffsetX, variation.decal.m_OffsetZ, m_Base->m_Properties.m_FloatOnWater); m_Model = new CModelDecal(objectManager.GetTerrain(), decal); return true; } if (!variation.particles.empty()) { m_Model = new CModelParticleEmitter(g_Renderer.GetParticleManager().LoadEmitterType(variation.particles)); return true; } std::vector props; for (std::multimap::iterator it = variation.props.begin(); it != variation.props.end(); ++it) props.push_back(it->second); // Build the model: // try and create a model CModelDefPtr modeldef (objectManager.GetMeshManager().GetMesh(m_ModelName)); if (!modeldef) { LOGERROR("CObjectEntry::BuildVariation(): Model %s failed to load", m_ModelName.string8()); return false; } // delete old model, create new CModel* model = new CModel(objectManager.GetSkeletonAnimManager(), m_Simulation); delete m_Model; m_Model = model; model->SetMaterial(g_Renderer.GetMaterialManager().LoadMaterial(m_Base->m_Material)); model->GetMaterial().AddStaticUniform("objectColor", CVector4D(m_Color.r, m_Color.g, m_Color.b, m_Color.a)); model->InitModel(modeldef); if (m_Samplers.empty()) LOGERROR("Actor '%s' has no textures.", utf8_from_wstring(m_Base->m_ShortName)); for (const auto& samp : m_Samplers) { CTextureProperties textureProps(samp.m_SamplerFile); textureProps.SetWrap(GL_CLAMP_TO_EDGE); CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps); // if we've loaded this model we're probably going to render it soon, so prefetch its texture. // All textures are prefetched even in the fixed pipeline, including the normal maps etc. // TODO: Should check which renderpath is selected and only preload the necessary textures. texture->Prefetch(); model->GetMaterial().AddSampler(CMaterial::TextureSampler(samp.m_SamplerName, texture)); } const std::vector& requiredSamplers = model->GetMaterial().GetRequiredSampler(); for (const auto& requSampName : requiredSamplers) { if (std::find_if(m_Samplers.begin(), m_Samplers.end(), [&](CObjectBase::Samp sampler) { return sampler.m_SamplerName == requSampName; }) == m_Samplers.end()) LOGERROR("Actor %s: required texture sampler %s not found (material %s)", utf8_from_wstring(m_Base->m_ShortName), requSampName.string().c_str(), m_Base->m_Material.string8().c_str()); } // calculate initial object space bounds, based on vertex positions model->CalcStaticObjectBounds(); // load the animations for (std::multimap::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it) { CStr name = it->first.LowerCase(); if (!it->second.m_FileName.empty()) { CSkeletonAnim* anim = model->BuildAnimation(it->second.m_FileName, name, it->second.m_Speed, it->second.m_ActionPos, it->second.m_ActionPos2, it->second.m_SoundPos); if (anim) m_Animations.insert(std::make_pair(name, anim)); } } // ensure there's always an idle animation if (m_Animations.find("idle") == m_Animations.end()) { CSkeletonAnim* anim = new CSkeletonAnim(); anim->m_Name = "idle"; anim->m_AnimDef = NULL; anim->m_Speed = 0.f; anim->m_ActionPos = 0.f; anim->m_ActionPos2 = 0.f; anim->m_SoundPos = 0.f; m_Animations.insert(std::make_pair("idle", anim)); // Ignore errors, since they're probably saying this is a non-animated model model->SetAnimation(anim); } else { // start up idling if (!model->SetAnimation(GetRandomAnimation("idle"))) LOGERROR("Failed to set idle animation in model \"%s\"", m_ModelName.string8()); } // build props - TODO, RC - need to fix up bounds here // TODO: Make sure random variations get handled correctly when a prop fails for (size_t p = 0; p < props.size(); p++) { const CObjectBase::Prop& prop = props[p]; // Pluck out the special attachpoint 'projectile' if (prop.m_PropPointName == "projectile") { m_ProjectileModelName = prop.m_ModelName; continue; } CObjectEntry* oe = objectManager.FindObjectVariation(prop.m_ModelName.c_str(), selections); if (!oe) { LOGERROR("Failed to build prop model \"%s\" on actor \"%s\"", utf8_from_wstring(prop.m_ModelName), utf8_from_wstring(m_Base->m_ShortName)); continue; } // If we don't have a projectile but this prop does (e.g. it's our rider), then // use that as our projectile too if (m_ProjectileModelName.empty() && !oe->m_ProjectileModelName.empty()) m_ProjectileModelName = oe->m_ProjectileModelName; CStr ppn = prop.m_PropPointName; bool isAmmo = false; // Handle the special attachpoint 'loaded-' if (ppn.Find("loaded-") == 0) { ppn = prop.m_PropPointName.substr(7); isAmmo = true; } const SPropPoint* proppoint = modeldef->FindPropPoint(ppn.c_str()); if (proppoint) { CModelAbstract* propmodel = oe->m_Model->Clone(); if (isAmmo) model->AddAmmoProp(proppoint, propmodel, oe); else model->AddProp(proppoint, propmodel, oe, prop.m_minHeight, prop.m_maxHeight, prop.m_selectable); if (propmodel->ToCModel()) propmodel->ToCModel()->SetAnimation(oe->GetRandomAnimation("idle")); } else LOGERROR("Failed to find matching prop point called \"%s\" in model \"%s\" for actor \"%s\"", ppn, m_ModelName.string8(), utf8_from_wstring(m_Base->m_ShortName)); } // setup flags if (m_Base->m_Properties.m_CastShadows) { model->SetFlags(model->GetFlags()|MODELFLAG_CASTSHADOWS); } return true; } CSkeletonAnim* CObjectEntry::GetRandomAnimation(const CStr& animationName) const { SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName); SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName); size_t count = std::distance(lower, upper); if (count == 0) return NULL; size_t id = rand(0, count); std::advance(lower, id); return lower->second; } std::vector CObjectEntry::GetAnimations(const CStr& animationName) const { std::vector anims; SkeletonAnimMap::const_iterator lower = m_Animations.lower_bound(animationName); SkeletonAnimMap::const_iterator upper = m_Animations.upper_bound(animationName); for (SkeletonAnimMap::const_iterator it = lower; it != upper; ++it) anims.push_back(it->second); return anims; } Index: ps/trunk/source/ps/CLogger.cpp =================================================================== --- ps/trunk/source/ps/CLogger.cpp (revision 16887) +++ ps/trunk/source/ps/CLogger.cpp (revision 16888) @@ -1,341 +1,342 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "CLogger.h" -#include "CConsole.h" + #include "graphics/FontMetrics.h" #include "graphics/ShaderManager.h" #include "graphics/TextRenderer.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/utf8.h" +#include "ps/CConsole.h" #include "ps/Profile.h" #include "renderer/Renderer.h" #include #include #include static const double RENDER_TIMEOUT = 10.0; // seconds before messages are deleted static const double RENDER_TIMEOUT_RATE = 10.0; // number of timed-out messages deleted per second static const size_t RENDER_LIMIT = 20; // maximum messages on screen at once // Set up a default logger that throws everything away, because that's // better than crashing. (This is particularly useful for unit tests which // don't care about any log output.) struct BlackHoleStreamBuf : public std::streambuf { } blackHoleStreamBuf; std::ostream blackHoleStream(&blackHoleStreamBuf); CLogger nullLogger(&blackHoleStream, &blackHoleStream, false, true); CLogger* g_Logger = &nullLogger; const char* html_header0 = "\n" "\n" "Pyrogenesis Log\n" "\n" "

0 A.D. "; const char* html_header1 = "

\n"; CLogger::CLogger() { OsPath mainlogPath(psLogDir()/"mainlog.html"); m_MainLog = new std::ofstream(OsString(mainlogPath).c_str(), std::ofstream::out | std::ofstream::trunc); OsPath interestinglogPath(psLogDir()/"interestinglog.html"); m_InterestingLog = new std::ofstream(OsString(interestinglogPath).c_str(), std::ofstream::out | std::ofstream::trunc); m_OwnsStreams = true; m_UseDebugPrintf = true; Init(); } CLogger::CLogger(std::ostream* mainLog, std::ostream* interestingLog, bool takeOwnership, bool useDebugPrintf) { m_MainLog = mainLog; m_InterestingLog = interestingLog; m_OwnsStreams = takeOwnership; m_UseDebugPrintf = useDebugPrintf; Init(); } void CLogger::Init() { m_RenderLastEraseTime = -1.0; // this is called too early to allow us to call timer_Time(), // so we'll fill in the initial value later m_NumberOfMessages = 0; m_NumberOfErrors = 0; m_NumberOfWarnings = 0; //Write Headers for the HTML documents *m_MainLog << html_header0 << "Main log" << html_header1; //Write Headers for the HTML documents *m_InterestingLog << html_header0 << "Main log (warnings and errors only)" << html_header1; } CLogger::~CLogger() { char buffer[128]; sprintf_s(buffer, ARRAY_SIZE(buffer), " with %d message(s), %d error(s) and %d warning(s).", m_NumberOfMessages,m_NumberOfErrors,m_NumberOfWarnings); time_t t = time(NULL); struct tm* now = localtime(&t); char currentDate[17]; sprintf_s(currentDate, ARRAY_SIZE(currentDate), "%04d-%02d-%02d", 1900+now->tm_year, 1+now->tm_mon, now->tm_mday); char currentTime[10]; sprintf_s(currentTime, ARRAY_SIZE(currentTime), "%02d:%02d:%02d", now->tm_hour, now->tm_min, now->tm_sec); //Write closing text *m_MainLog << "

Engine exited successfully on " << currentDate; *m_MainLog << " at " << currentTime << buffer << "

\n"; *m_InterestingLog << "

Engine exited successfully on " << currentDate; *m_InterestingLog << " at " << currentTime << buffer << "

\n"; if (m_OwnsStreams) { SAFE_DELETE(m_InterestingLog); SAFE_DELETE(m_MainLog); } } static std::string ToHTML(const char* message) { std::string cmessage = message; boost::algorithm::replace_all(cmessage, "&", "&"); boost::algorithm::replace_all(cmessage, "<", "<"); return cmessage; } void CLogger::WriteMessage(const char* message, bool doRender = false) { std::string cmessage = ToHTML(message); CScopeLock lock(m_Mutex); ++m_NumberOfMessages; // if (m_UseDebugPrintf) // debug_printf("MESSAGE: %s\n", message); *m_MainLog << "

" << cmessage << "

\n"; m_MainLog->flush(); if (doRender) { if (g_Console) g_Console->InsertMessage(std::string("INFO: ") + message); PushRenderMessage(Normal, message); } } void CLogger::WriteError(const char* message) { std::string cmessage = ToHTML(message); CScopeLock lock(m_Mutex); ++m_NumberOfErrors; if (m_UseDebugPrintf) debug_printf("ERROR: %s\n", message); if (g_Console) g_Console->InsertMessage(std::string("ERROR: ") + message); *m_InterestingLog << "

ERROR: " << cmessage << "

\n"; m_InterestingLog->flush(); *m_MainLog << "

ERROR: " << cmessage << "

\n"; m_MainLog->flush(); PushRenderMessage(Error, message); } void CLogger::WriteWarning(const char* message) { std::string cmessage = ToHTML(message); CScopeLock lock(m_Mutex); ++m_NumberOfWarnings; if (m_UseDebugPrintf) debug_printf("WARNING: %s\n", message); if (g_Console) g_Console->InsertMessage(std::string("WARNING: ") + message); *m_InterestingLog << "

WARNING: " << cmessage << "

\n"; m_InterestingLog->flush(); *m_MainLog << "

WARNING: " << cmessage << "

\n"; m_MainLog->flush(); PushRenderMessage(Warning, message); } void CLogger::Render() { PROFILE3_GPU("logger"); CleanupRenderQueue(); CStrIntern font_name("mono-stroke-10"); CFontMetrics font(font_name); int lineSpacing = font.GetLineSpacing(); CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text); textTech->BeginPass(); CTextRenderer textRenderer(textTech->GetShader()); textRenderer.Font(font_name); textRenderer.Color(1.0f, 1.0f, 1.0f); // Offset by an extra 35px vertically to avoid the top bar. textRenderer.Translate(4.0f, 35.0f + lineSpacing, 0.0f); // (Lock must come after loading the CFont, since that might log error messages // and attempt to lock the mutex recursively which is forbidden) CScopeLock lock(m_Mutex); - for (std::deque::iterator it = m_RenderMessages.begin(); it != m_RenderMessages.end(); ++it) + for (const RenderedMessage& msg : m_RenderMessages) { const char* type; - if (it->method == Normal) + if (msg.method == Normal) { type = "info"; textRenderer.Color(0.0f, 0.8f, 0.0f); } - else if (it->method == Warning) + else if (msg.method == Warning) { type = "warning"; textRenderer.Color(1.0f, 1.0f, 0.0f); } else { type = "error"; textRenderer.Color(1.0f, 0.0f, 0.0f); } CMatrix3D savedTransform = textRenderer.GetTransform(); - textRenderer.PrintfAdvance(L"[%8.3f] %hs: ", it->time, type); + textRenderer.PrintfAdvance(L"[%8.3f] %hs: ", msg.time, type); // Display the actual message in white so it's more readable textRenderer.Color(1.0f, 1.0f, 1.0f); - textRenderer.Put(0.0f, 0.0f, it->message.c_str()); + textRenderer.Put(0.0f, 0.0f, msg.message.c_str()); textRenderer.SetTransform(savedTransform); textRenderer.Translate(0.0f, (float)lineSpacing, 0.0f); } textRenderer.Render(); textTech->EndPass(); } void CLogger::PushRenderMessage(ELogMethod method, const char* message) { double now = timer_Time(); // Add each message line separately const char* pos = message; const char* eol; while ((eol = strchr(pos, '\n')) != NULL) { if (eol != pos) { RenderedMessage r = { method, now, std::string(pos, eol) }; m_RenderMessages.push_back(r); } pos = eol + 1; } // Add the last line, if we didn't end on a \n if (*pos != '\0') { RenderedMessage r = { method, now, std::string(pos) }; m_RenderMessages.push_back(r); } } void CLogger::CleanupRenderQueue() { CScopeLock lock(m_Mutex); if (m_RenderMessages.empty()) return; double now = timer_Time(); // Initialise the timer on the first call (since we can't do it in the ctor) if (m_RenderLastEraseTime == -1.0) m_RenderLastEraseTime = now; // Delete old messages, approximately at the given rate limit (and at most one per frame) if (now - m_RenderLastEraseTime > 1.0/RENDER_TIMEOUT_RATE) { if (m_RenderMessages[0].time + RENDER_TIMEOUT < now) { m_RenderMessages.pop_front(); m_RenderLastEraseTime = now; } } // If there's still too many then delete the oldest if (m_RenderMessages.size() > RENDER_LIMIT) m_RenderMessages.erase(m_RenderMessages.begin(), m_RenderMessages.end() - RENDER_LIMIT); } TestLogger::TestLogger() { m_OldLogger = g_Logger; g_Logger = new CLogger(&m_Stream, &blackHoleStream, false, false); } TestLogger::~TestLogger() { delete g_Logger; g_Logger = m_OldLogger; } std::string TestLogger::GetOutput() { return m_Stream.str(); } TestStdoutLogger::TestStdoutLogger() { m_OldLogger = g_Logger; g_Logger = new CLogger(&std::cout, &blackHoleStream, false, false); } TestStdoutLogger::~TestStdoutLogger() { delete g_Logger; g_Logger = m_OldLogger; } Index: ps/trunk/source/ps/ConfigDB.cpp =================================================================== --- ps/trunk/source/ps/ConfigDB.cpp (revision 16887) +++ ps/trunk/source/ps/ConfigDB.cpp (revision 16888) @@ -1,405 +1,399 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" +#include "ConfigDB.h" + #include -#include "CLogger.h" -#include "ConfigDB.h" -#include "Filesystem.h" -#include "ThreadUtil.h" #include "lib/allocators/shared_ptr.h" +#include "ps/CLogger.h" +#include "ps/Filesystem.h" +#include "ps/ThreadUtil.h" typedef std::map TConfigMap; TConfigMap CConfigDB::m_Map[CFG_LAST]; VfsPath CConfigDB::m_ConfigFile[CFG_LAST]; static pthread_mutex_t cfgdb_mutex = PTHREAD_MUTEX_INITIALIZER; CConfigDB::CConfigDB() { // Recursive mutex needed for WriteFile pthread_mutexattr_t attr; int err; err = pthread_mutexattr_init(&attr); ENSURE(err == 0); err = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); ENSURE(err == 0); err = pthread_mutex_init(&cfgdb_mutex, &attr); ENSURE(err == 0); err = pthread_mutexattr_destroy(&attr); ENSURE(err == 0); } #define CHECK_NS(rval)\ do {\ if (ns < 0 || ns >= CFG_LAST)\ {\ debug_warn(L"CConfigDB: Invalid ns value");\ return rval;\ }\ } while (false) namespace { template void Get(const CStr& value, T& ret) { std::stringstream ss(value); ss >> ret; } template<> void Get<>(const CStr& value, bool& ret) { ret = value == "true"; } template<> void Get<>(const CStr& value, std::string& ret) { ret = value; } std::string EscapeString(const CStr& str) { std::string ret; for (size_t i = 0; i < str.length(); ++i) { if (str[i] == '\\') ret += "\\\\"; else if (str[i] == '"') ret += "\\\""; else ret += str[i]; } return ret; } } // namespace #define GETVAL(type)\ void CConfigDB::GetValue(EConfigNamespace ns, const CStr& name, type& value)\ {\ CHECK_NS(;);\ CScopeLock s(&cfgdb_mutex);\ TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name);\ if (it != m_Map[CFG_COMMAND].end())\ {\ Get(it->second[0], value);\ return;\ }\ - for (int search_ns = ns; search_ns >= 0; search_ns--)\ + for (int search_ns = ns; search_ns >= 0; --search_ns)\ {\ it = m_Map[search_ns].find(name);\ if (it != m_Map[search_ns].end())\ {\ Get(it->second[0], value);\ return;\ }\ }\ } GETVAL(bool) GETVAL(int) GETVAL(float) GETVAL(double) GETVAL(std::string) #undef GETVAL -void CConfigDB::GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values) +void CConfigDB::GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values) const { CHECK_NS(;); CScopeLock s(&cfgdb_mutex); TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name); if (it != m_Map[CFG_COMMAND].end()) { values = it->second; return; } - for (int search_ns = ns; search_ns >= 0; search_ns--) + for (int search_ns = ns; search_ns >= 0; --search_ns) { it = m_Map[search_ns].find(name); if (it != m_Map[search_ns].end()) { values = it->second; return; } } } -EConfigNamespace CConfigDB::GetValueNamespace(EConfigNamespace ns, const CStr& name) +EConfigNamespace CConfigDB::GetValueNamespace(EConfigNamespace ns, const CStr& name) const { CHECK_NS(CFG_LAST); CScopeLock s(&cfgdb_mutex); TConfigMap::iterator it = m_Map[CFG_COMMAND].find(name); if (it != m_Map[CFG_COMMAND].end()) return CFG_COMMAND; - for (int search_ns = ns; search_ns >= 0; search_ns--) + for (int search_ns = ns; search_ns >= 0; --search_ns) { it = m_Map[search_ns].find(name); if (it != m_Map[search_ns].end()) return (EConfigNamespace)search_ns; } return CFG_LAST; } -std::map CConfigDB::GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix) +std::map CConfigDB::GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix) const { CScopeLock s(&cfgdb_mutex); std::map ret; CHECK_NS(ret); // Loop upwards so that values in later namespaces can override // values in earlier namespaces - for (int search_ns = 0; search_ns <= ns; search_ns++) - { - for (TConfigMap::iterator it = m_Map[search_ns].begin(); it != m_Map[search_ns].end(); ++it) - { - if (boost::algorithm::starts_with(it->first, prefix)) - ret[it->first] = it->second; - } - } - - for (TConfigMap::iterator it = m_Map[CFG_COMMAND].begin(); it != m_Map[CFG_COMMAND].end(); ++it) - { - if (boost::algorithm::starts_with(it->first, prefix)) - ret[it->first] = it->second; - } + for (int search_ns = 0; search_ns <= ns; ++search_ns) + for (const std::pair& p : m_Map[search_ns]) + if (boost::algorithm::starts_with(p.first, prefix)) + ret[p.first] = p.second; + + for (const std::pair& p : m_Map[CFG_COMMAND]) + if (boost::algorithm::starts_with(p.first, prefix)) + ret[p.first] = p.second; return ret; } void CConfigDB::SetValueString(EConfigNamespace ns, const CStr& name, const CStr& value) { CHECK_NS(;); CScopeLock s(&cfgdb_mutex); TConfigMap::iterator it = m_Map[ns].find(name); if (it == m_Map[ns].end()) it = m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet(1))); it->second[0] = value; } void CConfigDB::SetConfigFile(EConfigNamespace ns, const VfsPath& path) { CHECK_NS(;); CScopeLock s(&cfgdb_mutex); m_ConfigFile[ns] = path; } bool CConfigDB::Reload(EConfigNamespace ns) { CHECK_NS(false); CScopeLock s(&cfgdb_mutex); shared_ptr buffer; size_t buflen; { // Handle missing files quietly if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0) { LOGMESSAGE("Cannot find config file \"%s\" - ignoring", m_ConfigFile[ns].string8()); return false; } LOGMESSAGE("Loading config file \"%s\"", m_ConfigFile[ns].string8()); Status ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen); if (ret != INFO::OK) { LOGERROR("CConfigDB::Reload(): vfs_load for \"%s\" failed: return was %lld", m_ConfigFile[ns].string8(), (long long)ret); return false; } } TConfigMap newMap; char *filebuf = (char*)buffer.get(); char *filebufend = filebuf+buflen; bool quoted = false; CStr header; CStr name; CStr value; int line = 1; std::vector values; for (char* pos = filebuf; pos < filebufend; ++pos) { switch (*pos) { case '\n': case ';': break; // We finished parsing this line case ' ': case '\r': case '\t': continue; // ignore case '[': header.clear(); for (++pos; pos < filebufend && *pos != '\n' && *pos != ']'; ++pos) header.push_back(*pos); if (pos == filebufend || *pos == '\n') { LOGERROR("Config header with missing close tag encountered on line %d in '%s'", line, m_ConfigFile[ns].string8()); header.clear(); ++line; continue; } LOGMESSAGE("Found config header '%s'", header.c_str()); header.push_back('.'); while (++pos < filebufend && *pos != '\n' && *pos != ';') if (*pos != ' ' && *pos != '\r') { LOGERROR("Config settings on the same line as a header on line %d in '%s'", line, m_ConfigFile[ns].string8()); break; } while (pos < filebufend && *pos != '\n') ++pos; ++line; continue; case '=': // Parse parameters (comma separated, possibly quoted) for (++pos; pos < filebufend && *pos != '\n' && *pos != ';'; ++pos) { switch (*pos) { case '"': quoted = true; // parse until not quoted anymore for (++pos; pos < filebufend && *pos != '\n' && *pos != '"'; ++pos) { if (*pos == '\\' && ++pos == filebufend) { LOGERROR("Escape character at end of input (line %d in '%s')", line, m_ConfigFile[ns].string8()); break; } value.push_back(*pos); } if (pos < filebufend && *pos == '"') quoted = false; else --pos; // We should terminate the outer loop too break; case ' ': case '\r': case '\t': break; // ignore case ',': if (!value.empty()) values.push_back(value); value.clear(); break; default: value.push_back(*pos); break; } } if (quoted) // We ignore the invalid parameter LOGERROR("Unmatched quote while parsing config file '%s' on line %d", m_ConfigFile[ns].string8(), line); else if (!value.empty()) values.push_back(value); value.clear(); quoted = false; break; // We are either at the end of the line, or we still have a comment to parse default: name.push_back(*pos); continue; } // Consume the rest of the line while (pos < filebufend && *pos != '\n') ++pos; // Store the setting if (!name.empty() && !values.empty()) { CStr key(header + name); newMap[key] = values; if (key == "lobby.password") LOGMESSAGE("Loaded config string \"%s\"", key); else { std::string vals; for (size_t i = 0; i < newMap[key].size() - 1; ++i) vals += "\"" + EscapeString(newMap[key][i]) + "\", "; vals += "\"" + EscapeString(newMap[key][values.size()-1]) + "\""; LOGMESSAGE("Loaded config string \"%s\" = %s", key, vals); } } else if (!name.empty()) LOGERROR("Encountered config setting '%s' without value while parsing '%s' on line %d", name, m_ConfigFile[ns].string8(), line); name.clear(); values.clear(); ++line; } if (!name.empty()) LOGERROR("Config file does not have a new line after the last config setting '%s'", name); m_Map[ns].swap(newMap); return true; } -bool CConfigDB::WriteFile(EConfigNamespace ns) +bool CConfigDB::WriteFile(EConfigNamespace ns) const { CHECK_NS(false); CScopeLock s(&cfgdb_mutex); return WriteFile(ns, m_ConfigFile[ns]); } -bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) +bool CConfigDB::WriteFile(EConfigNamespace ns, const VfsPath& path) const { CHECK_NS(false); CScopeLock s(&cfgdb_mutex); shared_ptr buf; AllocateAligned(buf, 1*MiB, maxSectorSize); char* pos = (char*)buf.get(); - TConfigMap &map = m_Map[ns]; - for (TConfigMap::const_iterator it = map.begin(); it != map.end(); ++it) + for (const std::pair& p : m_Map[ns]) { size_t i; - pos += sprintf(pos, "%s = ", it->first.c_str()); - for (i = 0; i < it->second.size() - 1; ++i) - pos += sprintf(pos, "\"%s\", ", EscapeString(it->second[i]).c_str()); - pos += sprintf(pos, "\"%s\"\n", EscapeString(it->second[i]).c_str()); + pos += sprintf(pos, "%s = ", p.first.c_str()); + for (i = 0; i < p.second.size() - 1; ++i) + pos += sprintf(pos, "\"%s\", ", EscapeString(p.second[i]).c_str()); + pos += sprintf(pos, "\"%s\"\n", EscapeString(p.second[i]).c_str()); } const size_t len = pos - (char*)buf.get(); Status ret = g_VFS->CreateFile(path, buf, len); if (ret < 0) { LOGERROR("CConfigDB::WriteFile(): CreateFile \"%s\" failed (error: %d)", path.string8(), (int)ret); return false; } return true; } #undef CHECK_NS Index: ps/trunk/source/ps/ConfigDB.h =================================================================== --- ps/trunk/source/ps/ConfigDB.h (revision 16887) +++ ps/trunk/source/ps/ConfigDB.h (revision 16888) @@ -1,147 +1,146 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* CConfigDB - Load, access and store configuration variables TDD : http://www.wildfiregames.com/forum/index.php?showtopic=1125 OVERVIEW: JavaScript: Check this documentation: http://trac.wildfiregames.com/wiki/Exposed_ConfigDB_Functions */ #ifndef INCLUDED_CONFIGDB #define INCLUDED_CONFIGDB -#include "CStr.h" -#include "Singleton.h" - #include "lib/file/vfs/vfs_path.h" +#include "ps/CStr.h" +#include "ps/Singleton.h" // Namespace priorities: User supersedes mod supersedes system. // Command-line arguments override everything. enum EConfigNamespace { CFG_DEFAULT, CFG_SYSTEM, CFG_MOD, CFG_USER, CFG_COMMAND, CFG_LAST }; typedef std::vector CConfigValueSet; #define g_ConfigDB CConfigDB::GetSingleton() class CConfigDB: public Singleton { static std::map m_Map[]; static VfsPath m_ConfigFile[]; public: CConfigDB(); /** * Attempt to retrieve the value of a config variable with the given name; * will search CFG_COMMAND first, and then all namespaces from the specified * namespace down. */ void GetValue(EConfigNamespace ns, const CStr& name, bool& value); ///@copydoc CConfigDB::GetValue void GetValue(EConfigNamespace ns, const CStr& name, int& value); ///@copydoc CConfigDB::GetValue void GetValue(EConfigNamespace ns, const CStr& name, float& value); ///@copydoc CConfigDB::GetValue void GetValue(EConfigNamespace ns, const CStr& name, double& value); ///@copydoc CConfigDB::GetValue void GetValue(EConfigNamespace ns, const CStr& name, std::string& value); /** * Attempt to retrieve a vector of values corresponding to the given setting; * will search CFG_COMMAND first, and then all namespaces from the specified * namespace down. */ - void GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values); + void GetValues(EConfigNamespace ns, const CStr& name, CConfigValueSet& values) const; /** * Returns the namespace that the value returned by GetValues was defined in, * or CFG_LAST if it wasn't defined at all. */ - EConfigNamespace GetValueNamespace(EConfigNamespace ns, const CStr& name); + EConfigNamespace GetValueNamespace(EConfigNamespace ns, const CStr& name) const; /** * Retrieve a map of values corresponding to settings whose names begin * with the given prefix; * will search all namespaces from default up to the specified namespace. */ - std::map GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix); + std::map GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix) const; /** * Save a config value in the specified namespace. If the config variable * existed the value is replaced. */ void SetValueString(EConfigNamespace ns, const CStr& name, const CStr& value); /** * Set the path to the config file used to populate the specified namespace * Note that this function does not actually load the config file. Use * the Reload() method if you want to read the config file at the same time. * * 'path': The path to the config file. */ void SetConfigFile(EConfigNamespace ns, const VfsPath& path); /** * Reload the config file associated with the specified config namespace * (the last config file path set with SetConfigFile) * * Returns: * true: if the reload succeeded, * false: if the reload failed */ bool Reload(EConfigNamespace); /** * Write the current state of the specified config namespace to the file * specified by 'path' * * Returns: * true: if the config namespace was successfully written to the file * false: if an error occurred */ - bool WriteFile(EConfigNamespace ns, const VfsPath& path); + bool WriteFile(EConfigNamespace ns, const VfsPath& path) const; /** * Write the current state of the specified config namespace to the file * it was originally loaded from. * * Returns: * true: if the config namespace was successfully written to the file * false: if an error occurred */ - bool WriteFile(EConfigNamespace ns); + bool WriteFile(EConfigNamespace ns) const; }; // stores the value of the given key into . this quasi-template // convenience wrapper on top of GetValue simplifies user code #define CFG_GET_VAL(name, destination)\ g_ConfigDB.GetValue(CFG_USER, name, destination) -#endif +#endif // INCLUDED_CONFIGDB Index: ps/trunk/source/ps/Hotkey.cpp =================================================================== --- ps/trunk/source/ps/Hotkey.cpp (revision 16887) +++ ps/trunk/source/ps/Hotkey.cpp (revision 16888) @@ -1,367 +1,361 @@ -/* Copyright (C) 2014 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Hotkey.h" #include #include "lib/input.h" -#include "ConfigDB.h" -#include "CLogger.h" -#include "CConsole.h" -#include "CStr.h" +#include "ps/CConsole.h" +#include "ps/CLogger.h" +#include "ps/CStr.h" +#include "ps/ConfigDB.h" #include "ps/Globals.h" -#include "KeyName.h" +#include "ps/KeyName.h" static bool unified[UNIFIED_LAST - UNIFIED_SHIFT]; #if SDL_VERSION_ATLEAST(2, 0, 0) #define SDLKEY SDL_Keycode #else #define SDLKEY SDLKey #endif struct SKey { SDLKEY code; // keycode or MOUSE_ or UNIFIED_ value bool negated; // whether the key must be pressed (false) or unpressed (true) }; // Hotkey data associated with an externally-specified 'primary' keycode struct SHotkeyMapping { CStr name; // name of the hotkey bool negated; // whether the primary key must be pressed (false) or unpressed (true) std::vector requires; // list of non-primary keys that must also be active }; typedef std::vector KeyMapping; // A mapping of keycodes onto the hotkeys that are associated with that key. // (A hotkey triggered by a combination of multiple keys will be in this map // multiple times.) static std::map g_HotkeyMap; // The current pressed status of hotkeys std::map g_HotkeyStatus; // Look up each key binding in the config file and set the mappings for // all key combinations that trigger it. static void LoadConfigBindings() { - std::map bindings = g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey."); - for (std::map::iterator bindingsIt = bindings.begin(); bindingsIt != bindings.end(); ++bindingsIt) + for (const std::pair& configPair : g_ConfigDB.GetValuesWithPrefix(CFG_COMMAND, "hotkey.")) { - std::string hotkeyName = bindingsIt->first.substr(7); // strip the "hotkey." prefix - for (CConfigValueSet::iterator it = bindingsIt->second.begin(); it != bindingsIt->second.end(); ++it) + std::string hotkeyName = configPair.first.substr(7); // strip the "hotkey." prefix + for (const CStr& hotkey : configPair.second) { - const CStr& hotkey = *it; std::vector keyCombination; // Iterate through multiple-key bindings (e.g. Ctrl+I) boost::char_separator sep("+"); typedef boost::tokenizer > tokenizer; tokenizer tok(hotkey, sep); for (tokenizer::iterator it = tok.begin(); it != tok.end(); ++it) { // Attempt decode as key name int mapping = FindKeyCode(*it); if (!mapping) { LOGWARNING("Hotkey mapping used invalid key '%s'", hotkey.c_str()); continue; } SKey key = { (SDLKEY)mapping, false }; keyCombination.push_back(key); } std::vector::iterator itKey, itKey2; for (itKey = keyCombination.begin(); itKey != keyCombination.end(); ++itKey) { SHotkeyMapping bindCode; bindCode.name = hotkeyName; bindCode.negated = itKey->negated; for (itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); ++itKey2) if (itKey != itKey2) // Push any auxiliary keys bindCode.requires.push_back(*itKey2); g_HotkeyMap[itKey->code].push_back(bindCode); } } } } void LoadHotkeys() { InitKeyNameMap(); LoadConfigBindings(); // Set up the state of the hotkeys given no key is down. // i.e. find those hotkeys triggered by all negations. - for( std::map::iterator mapIt = g_HotkeyMap.begin(); mapIt != g_HotkeyMap.end(); ++mapIt ) - { - KeyMapping& hotkeyMap = mapIt->second; - - for( std::vector::iterator it = hotkeyMap.begin(); it != hotkeyMap.end(); ++it ) + for (const std::pair& p : g_HotkeyMap) + for (const SHotkeyMapping& hotkey : p.second) { - if( !it->negated ) + if (!hotkey.negated) continue; bool allNegated = true; - for( std::vector::iterator j = it->requires.begin(); j != it->requires.end(); ++j ) - if( !j->negated ) + for (const SKey& k : hotkey.requires) + if (!k.negated) allNegated = false; - if( allNegated ) - g_HotkeyStatus[it->name] = true; + if (allNegated) + g_HotkeyStatus[hotkey.name] = true; } - } } void UnloadHotkeys() { g_HotkeyMap.clear(); g_HotkeyStatus.clear(); } bool isNegated(const SKey& key) { // Normal keycodes are below EXTRA_KEYS_BASE if ((int)key.code < EXTRA_KEYS_BASE && g_keys[key.code] == key.negated) return false; // Mouse 'keycodes' are after the modifier keys else if ((int)key.code > UNIFIED_LAST && g_mouse_buttons[key.code - UNIFIED_LAST] == key.negated) return false; // Modifier keycodes are between the normal keys and the mouse 'keys' else if ((int)key.code < UNIFIED_LAST && (int)key.code > CUSTOM_SDL_KEYCODE && unified[key.code - UNIFIED_SHIFT] == key.negated) return false; else return true; } -InReaction HotkeyInputHandler( const SDL_Event_* ev ) +InReaction HotkeyInputHandler(const SDL_Event_* ev) { int keycode = 0; - switch( ev->ev.type ) + switch(ev->ev.type) { case SDL_KEYDOWN: case SDL_KEYUP: keycode = (int)ev->ev.key.keysym.sym; break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: #if SDL_VERSION_ATLEAST(2, 0, 0) // Mousewheel events are no longer buttons, but we want to maintain the order // expected by g_mouse_buttons for compatibility if (ev->ev.button.button >= SDL_BUTTON_X1) keycode = MOUSE_BASE + (int)ev->ev.button.button + 2; else #endif keycode = MOUSE_BASE + (int)ev->ev.button.button; break; #if SDL_VERSION_ATLEAST(2, 0, 0) case SDL_MOUSEWHEEL: if (ev->ev.wheel.y > 0) { keycode = MOUSE_WHEELUP; break; } else if (ev->ev.wheel.y < 0) { keycode = MOUSE_WHEELDOWN; break; } else if (ev->ev.wheel.x > 0) { keycode = MOUSE_X2; break; } else if (ev->ev.wheel.x < 0) { keycode = MOUSE_X1; break; } return IN_PASS; #endif case SDL_HOTKEYDOWN: g_HotkeyStatus[static_cast(ev->ev.user.data1)] = true; return IN_PASS; case SDL_HOTKEYUP: g_HotkeyStatus[static_cast(ev->ev.user.data1)] = false; return IN_PASS; default: return IN_PASS; } #if !SDL_VERSION_ATLEAST(2, 0, 0) // Rather ugly hack to make the '"' key work better on a MacBook Pro on Windows so it doesn't // always close the console. (Maybe this would be better handled in wsdl or something?) if (keycode == SDLK_BACKQUOTE && (ev->ev.key.keysym.unicode == '\'' || ev->ev.key.keysym.unicode == '"')) keycode = ev->ev.key.keysym.unicode; #endif // Somewhat hackish: // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed // Just send them to this handler; don't let the imaginary event codes leak back to real SDL. SDL_Event_ phantom; - phantom.ev.type = ( ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) ) ? SDL_KEYDOWN : SDL_KEYUP; - if( ( keycode == SDLK_LSHIFT ) || ( keycode == SDLK_RSHIFT ) ) + phantom.ev.type = ((ev->ev.type == SDL_KEYDOWN) || (ev->ev.type == SDL_MOUSEBUTTONDOWN)) ? SDL_KEYDOWN : SDL_KEYUP; + if ((keycode == SDLK_LSHIFT) || (keycode == SDLK_RSHIFT)) { phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_SHIFT; - unified[0] = ( phantom.ev.type == SDL_KEYDOWN ); - HotkeyInputHandler( &phantom ); + unified[0] = (phantom.ev.type == SDL_KEYDOWN); + HotkeyInputHandler(&phantom); } - else if( ( keycode == SDLK_LCTRL ) || ( keycode == SDLK_RCTRL ) ) + else if ((keycode == SDLK_LCTRL) || (keycode == SDLK_RCTRL)) { phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_CTRL; - unified[1] = ( phantom.ev.type == SDL_KEYDOWN ); - HotkeyInputHandler( &phantom ); + unified[1] = (phantom.ev.type == SDL_KEYDOWN); + HotkeyInputHandler(&phantom); } - else if( ( keycode == SDLK_LALT ) || ( keycode == SDLK_RALT ) ) + else if ((keycode == SDLK_LALT) || (keycode == SDLK_RALT)) { phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_ALT; - unified[2] = ( phantom.ev.type == SDL_KEYDOWN ); - HotkeyInputHandler( &phantom ); + unified[2] = (phantom.ev.type == SDL_KEYDOWN); + HotkeyInputHandler(&phantom); } #if SDL_VERSION_ATLEAST(2, 0, 0) - else if( ( keycode == SDLK_LGUI ) || ( keycode == SDLK_RGUI ) ) + else if ((keycode == SDLK_LGUI) || (keycode == SDLK_RGUI)) #else // SDL 1.2 - else if( ( keycode == SDLK_LSUPER ) || ( keycode == SDLK_RSUPER ) || ( keycode == SDLK_LMETA ) || ( keycode == SDLK_RMETA) ) + else if ((keycode == SDLK_LSUPER) || (keycode == SDLK_RSUPER) || (keycode == SDLK_LMETA) || (keycode == SDLK_RMETA)) #endif { phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_SUPER; - unified[3] = ( phantom.ev.type == SDL_KEYDOWN ); - HotkeyInputHandler( &phantom ); + unified[3] = (phantom.ev.type == SDL_KEYDOWN); + HotkeyInputHandler(&phantom); } // Check whether we have any hotkeys registered for this particular keycode - if( g_HotkeyMap.find(keycode) == g_HotkeyMap.end() ) - return( IN_PASS ); + if (g_HotkeyMap.find(keycode) == g_HotkeyMap.end()) + return (IN_PASS); // Inhibit the dispatch of hotkey events caused by real keys (not fake mouse button // events) while the console is up. bool consoleCapture = false; - if( g_Console->IsActive() && keycode < CUSTOM_SDL_KEYCODE ) + if (g_Console->IsActive() && keycode < CUSTOM_SDL_KEYCODE) consoleCapture = true; // Here's an interesting bit: // If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing // 'F' while control is down would normally fire off both. // To avoid this, set the modifier keys for /all/ events this key would trigger // (Ctrl, for example, is both group-save and bookmark-save) // but only send a HotkeyDown event for the event with bindings most precisely // matching the conditions (i.e. the event with the highest number of auxiliary // keys, providing they're all down) #if SDL_VERSION_ATLEAST(2, 0, 0) bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL); #else bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ); #endif // -- KEYDOWN SECTION -- std::vector closestMapNames; size_t closestMapMatch = 0; - for (std::vector::iterator it = g_HotkeyMap[keycode].begin(); it < g_HotkeyMap[keycode].end(); ++it) + for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode]) { // If a key has been pressed, and this event triggers on its release, skip it. // Similarly, if the key's been released and the event triggers on a keypress, skip it. - if (it->negated == typeKeyDown) + if (hotkey.negated == typeKeyDown) continue; // Check for no unpermitted keys bool accept = true; - for (std::vector::iterator itKey = it->requires.begin(); itKey != it->requires.end() && accept; ++itKey) - accept = isNegated(*itKey); + for (const SKey& k : hotkey.requires) + accept = isNegated(k); - if (accept && !(consoleCapture && it->name != "console.toggle")) + if (accept && !(consoleCapture && hotkey.name != "console.toggle")) { // Check if this is an equally precise or more precise match - if (it->requires.size() + 1 >= closestMapMatch) + if (hotkey.requires.size() + 1 >= closestMapMatch) { // Check if more precise - if (it->requires.size() + 1 > closestMapMatch) + if (hotkey.requires.size() + 1 > closestMapMatch) { // Throw away the old less-precise matches closestMapNames.clear(); - closestMapMatch = it->requires.size() + 1; + closestMapMatch = hotkey.requires.size() + 1; } - closestMapNames.push_back(it->name.c_str()); + closestMapNames.push_back(hotkey.name.c_str()); } } } for (size_t i = 0; i < closestMapNames.size(); ++i) { SDL_Event_ hotkeyNotification; hotkeyNotification.ev.type = SDL_HOTKEYDOWN; hotkeyNotification.ev.user.data1 = const_cast(closestMapNames[i]); in_push_priority_event(&hotkeyNotification); } // -- KEYUP SECTION -- - for (std::vector::iterator it = g_HotkeyMap[keycode].begin(); it < g_HotkeyMap[keycode].end(); ++it) + for (const SHotkeyMapping& hotkey : g_HotkeyMap[keycode]) { // If it's a keydown event, won't cause HotKeyUps in anything that doesn't // use this key negated => skip them // If it's a keyup event, won't cause HotKeyUps in anything that does use // this key negated => skip them too. - if (it->negated != typeKeyDown) + if (hotkey.negated != typeKeyDown) continue; // Check for no unpermitted keys bool accept = true; - for (std::vector::iterator itKey = it->requires.begin(); itKey != it->requires.end() && accept; ++itKey) - accept = isNegated(*itKey); + for (const SKey& k : hotkey.requires) + accept = isNegated(k); if (accept) { SDL_Event_ hotkeyNotification; hotkeyNotification.ev.type = SDL_HOTKEYUP; - hotkeyNotification.ev.user.data1 = const_cast(it->name.c_str()); + hotkeyNotification.ev.user.data1 = const_cast(hotkey.name.c_str()); in_push_priority_event(&hotkeyNotification); } } return IN_PASS; } bool HotkeyIsPressed(const CStr& keyname) { return g_HotkeyStatus[keyname]; } Index: ps/trunk/source/ps/ProfileViewer.cpp =================================================================== --- ps/trunk/source/ps/ProfileViewer.cpp (revision 16887) +++ ps/trunk/source/ps/ProfileViewer.cpp (revision 16888) @@ -1,640 +1,637 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Implementation of profile display (containing only display routines, * the data model(s) are implemented elsewhere). */ #include "precompiled.h" #include #include #include "ProfileViewer.h" #include "graphics/FontMetrics.h" #include "gui/GUIutil.h" #include "graphics/ShaderManager.h" #include "graphics/TextRenderer.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Hotkey.h" #include "ps/Profile.h" #include "lib/external_libraries/libsdl.h" #include "renderer/Renderer.h" #include "scriptinterface/ScriptInterface.h" extern int g_xres, g_yres; struct CProfileViewerInternals { + NONCOPYABLE(CProfileViewerInternals); // because of the ofstream +public: CProfileViewerInternals() {} /// Whether the profiling display is currently visible bool profileVisible; /// List of root tables std::vector rootTables; /// Path from a root table (path[0]) to the currently visible table (path[size-1]) std::vector path; /// Helper functions void TableIsDeleted(AbstractProfileTable* table); void NavigateTree(int id); /// File for saved profile output (reset when the game is restarted) std::ofstream outputStream; - -private: - // Cannot be copied/assigned, because of the ofstream - CProfileViewerInternals(const CProfileViewerInternals& rhs); - const CProfileViewerInternals& operator=(const CProfileViewerInternals& rhs); }; /////////////////////////////////////////////////////////////////////////////////////////////// // AbstractProfileTable implementation AbstractProfileTable::~AbstractProfileTable() { if (CProfileViewer::IsInitialised()) { g_ProfileViewer.m->TableIsDeleted(this); } } /////////////////////////////////////////////////////////////////////////////////////////////// // CProfileViewer implementation // AbstractProfileTable got deleted, make sure we have no dangling pointers void CProfileViewerInternals::TableIsDeleted(AbstractProfileTable* table) { for(int idx = (int)rootTables.size()-1; idx >= 0; --idx) { if (rootTables[idx] == table) rootTables.erase(rootTables.begin() + idx); } for(size_t idx = 0; idx < path.size(); ++idx) { if (path[idx] != table) continue; path.erase(path.begin() + idx, path.end()); if (path.size() == 0) profileVisible = false; } } // Move into child tables or return to parent tables based on the given number void CProfileViewerInternals::NavigateTree(int id) { if (id == 0) { if (path.size() > 1) path.pop_back(); } else { AbstractProfileTable* table = path[path.size() - 1]; size_t numrows = table->GetNumberRows(); for(size_t row = 0; row < numrows; ++row) { AbstractProfileTable* child = table->GetChild(row); if (!child) continue; --id; if (id == 0) { path.push_back(child); break; } } } } // Construction/Destruction CProfileViewer::CProfileViewer() { m = new CProfileViewerInternals; m->profileVisible = false; } CProfileViewer::~CProfileViewer() { delete m; } // Render void CProfileViewer::RenderProfile() { if (!m->profileVisible) return; if (!m->path.size()) { m->profileVisible = false; return; } PROFILE3_GPU("profile viewer"); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); AbstractProfileTable* table = m->path[m->path.size() - 1]; const std::vector& columns = table->GetColumns(); size_t numrows = table->GetNumberRows(); CStrIntern font_name("mono-stroke-10"); CFontMetrics font(font_name); int lineSpacing = font.GetLineSpacing(); // Render background GLint estimate_height; GLint estimate_width; estimate_width = 50; for(size_t i = 0; i < columns.size(); ++i) estimate_width += (GLint)columns[i].width; estimate_height = 3 + (GLint)numrows; if (m->path.size() > 1) estimate_height += 2; estimate_height = lineSpacing*estimate_height; CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid); solidTech->BeginPass(); CShaderProgramPtr solidShader = solidTech->GetShader(); solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.5f); CMatrix3D transform = GetDefaultGuiMatrix(); solidShader->Uniform(str_transform, transform); float backgroundVerts[] = { (float)estimate_width, 0.0f, 0.0f, 0.0f, 0.0f, (float)estimate_height, 0.0f, (float)estimate_height, (float)estimate_width, (float)estimate_height, (float)estimate_width, 0.0f }; solidShader->VertexPointer(2, GL_FLOAT, 0, backgroundVerts); solidShader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); transform.PostTranslate(22.0f, lineSpacing*3.0f, 0.0f); solidShader->Uniform(str_transform, transform); // Draw row backgrounds for (size_t row = 0; row < numrows; ++row) { if (row % 2) solidShader->Uniform(str_color, 1.0f, 1.0f, 1.0f, 0.1f); else solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.1f); float rowVerts[] = { -22.f, 2.f, estimate_width-22.f, 2.f, estimate_width-22.f, 2.f-lineSpacing, estimate_width-22.f, 2.f-lineSpacing, -22.f, 2.f-lineSpacing, -22.f, 2.f }; solidShader->VertexPointer(2, GL_FLOAT, 0, rowVerts); solidShader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6); transform.PostTranslate(0.0f, lineSpacing, 0.0f); solidShader->Uniform(str_transform, transform); } solidTech->EndPass(); // Print table and column titles CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text); textTech->BeginPass(); CTextRenderer textRenderer(textTech->GetShader()); textRenderer.Font(font_name); textRenderer.Color(1.0f, 1.0f, 1.0f); textRenderer.PrintfAt(2.0f, lineSpacing, L"%hs", table->GetTitle().c_str()); textRenderer.Translate(22.0f, lineSpacing*2.0f, 0.0f); float colX = 0.0f; for (size_t col = 0; col < columns.size(); ++col) { CStrW text = columns[col].title.FromUTF8(); int w, h; font.CalculateStringSize(text.c_str(), w, h); float x = colX; if (col > 0) // right-align all but the first column x += columns[col].width - w; textRenderer.Put(x, 0.0f, text.c_str()); colX += columns[col].width; } textRenderer.Translate(0.0f, lineSpacing, 0.0f); // Print rows int currentExpandId = 1; for (size_t row = 0; row < numrows; ++row) { if (table->IsHighlightRow(row)) textRenderer.Color(1.0f, 0.5f, 0.5f); else textRenderer.Color(1.0f, 1.0f, 1.0f); if (table->GetChild(row)) { textRenderer.PrintfAt(-15.0f, 0.0f, L"%d", currentExpandId); currentExpandId++; } float colX = 0.0f; for (size_t col = 0; col < columns.size(); ++col) { CStrW text = table->GetCellText(row, col).FromUTF8(); int w, h; font.CalculateStringSize(text.c_str(), w, h); float x = colX; if (col > 0) // right-align all but the first column x += columns[col].width - w; textRenderer.Put(x, 0.0f, text.c_str()); colX += columns[col].width; } textRenderer.Translate(0.0f, lineSpacing, 0.0f); } textRenderer.Color(1.0f, 1.0f, 1.0f); if (m->path.size() > 1) { textRenderer.Translate(0.0f, lineSpacing, 0.0f); textRenderer.Put(-15.0f, 0.0f, L"0"); textRenderer.Put(0.0f, 0.0f, L"back to parent"); } textRenderer.Render(); textTech->EndPass(); glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); } // Handle input InReaction CProfileViewer::Input(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_KEYDOWN: { if (!m->profileVisible) break; int k = ev->ev.key.keysym.sym; if (k >= SDLK_0 && k <= SDLK_9) { m->NavigateTree(k - SDLK_0); return IN_HANDLED; } break; } case SDL_HOTKEYDOWN: std::string hotkey = static_cast(ev->ev.user.data1); if( hotkey == "profile.toggle" ) { if (!m->profileVisible) { if (m->rootTables.size()) { m->profileVisible = true; m->path.push_back(m->rootTables[0]); } } else { size_t i; for(i = 0; i < m->rootTables.size(); ++i) { if (m->rootTables[i] == m->path[0]) break; } i++; m->path.clear(); if (i < m->rootTables.size()) { m->path.push_back(m->rootTables[i]); } else { m->profileVisible = false; } } return( IN_HANDLED ); } else if( hotkey == "profile.save" ) { SaveToFile(); return( IN_HANDLED ); } break; } return( IN_PASS ); } InReaction CProfileViewer::InputThunk(const SDL_Event_* ev) { if (CProfileViewer::IsInitialised()) return g_ProfileViewer.Input(ev); return IN_PASS; } // Add a table to the list of roots void CProfileViewer::AddRootTable(AbstractProfileTable* table, bool front) { if (front) m->rootTables.insert(m->rootTables.begin(), table); else m->rootTables.push_back(table); } namespace { struct WriteTable { std::ofstream& f; WriteTable(std::ofstream& f) : f(f) {} void operator() (AbstractProfileTable* table) { std::vector data; // 2d array of (rows+head)*columns elements const std::vector& columns = table->GetColumns(); // Add column headers to 'data' for (std::vector::const_iterator col_it = columns.begin(); col_it != columns.end(); ++col_it) data.push_back(col_it->title); // Recursively add all profile data to 'data' WriteRows(1, table, data); // Calculate the width of each column ( = the maximum width of // any value in that column) std::vector columnWidths; size_t cols = columns.size(); for (size_t c = 0; c < cols; ++c) { size_t max = 0; for (size_t i = c; i < data.size(); i += cols) max = std::max(max, data[i].length()); columnWidths.push_back(max); } // Output data as a formatted table: f << "\n\n" << table->GetTitle() << "\n"; if (cols == 0) // avoid divide-by-zero return; for (size_t r = 0; r < data.size()/cols; ++r) { for (size_t c = 0; c < cols; ++c) f << (c ? " | " : "\n") << data[r*cols + c].Pad(PS_TRIM_RIGHT, columnWidths[c]); // Add dividers under some rows. (Currently only the first, since // that contains the column headers.) if (r == 0) for (size_t c = 0; c < cols; ++c) f << (c ? "-|-" : "\n") << CStr::Repeat("-", columnWidths[c]); } } void WriteRows(int indent, AbstractProfileTable* table, std::vector& data) { const std::vector& columns = table->GetColumns(); for (size_t r = 0; r < table->GetNumberRows(); ++r) { // Do pretty tree-structure indenting CStr indentation = CStr::Repeat("| ", indent-1); if (r+1 == table->GetNumberRows()) indentation += "'-"; else indentation += "|-"; for (size_t c = 0; c < columns.size(); ++c) if (c == 0) data.push_back(indentation + table->GetCellText(r, c)); else data.push_back(table->GetCellText(r, c)); if (table->GetChild(r)) WriteRows(indent+1, table->GetChild(r), data); } } private: const WriteTable& operator=(const WriteTable&); }; struct DumpTable { ScriptInterface& m_ScriptInterface; JS::PersistentRooted m_Root; DumpTable(ScriptInterface& scriptInterface, JS::HandleValue root) : m_ScriptInterface(scriptInterface), m_Root(scriptInterface.GetJSRuntime(), root) { } // std::for_each requires a move constructor and the use of JS::PersistentRooted apparently breaks a requirement for an // automatic move constructor DumpTable(DumpTable && original) : m_ScriptInterface(original.m_ScriptInterface), m_Root(original.m_ScriptInterface.GetJSRuntime(), original.m_Root.get()) { } void operator() (AbstractProfileTable* table) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue t(cx); JS::RootedValue rows(cx, DumpRows(table)); m_ScriptInterface.Eval(L"({})", &t); m_ScriptInterface.SetProperty(t, "cols", DumpCols(table)); m_ScriptInterface.SetProperty(t, "data", rows); m_ScriptInterface.SetProperty(m_Root, table->GetTitle().c_str(), t); } std::vector DumpCols(AbstractProfileTable* table) { std::vector titles; const std::vector& columns = table->GetColumns(); for (size_t c = 0; c < columns.size(); ++c) titles.push_back(columns[c].title); return titles; } JS::Value DumpRows(AbstractProfileTable* table) { JSContext* cx = m_ScriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue data(cx); m_ScriptInterface.Eval("({})", &data); const std::vector& columns = table->GetColumns(); for (size_t r = 0; r < table->GetNumberRows(); ++r) { JS::RootedValue row(cx); m_ScriptInterface.Eval("([])", &row); m_ScriptInterface.SetProperty(data, table->GetCellText(r, 0).c_str(), row); if (table->GetChild(r)) { JS::RootedValue childRows(cx, DumpRows(table->GetChild(r))); m_ScriptInterface.SetPropertyInt(row, 0, childRows); } for (size_t c = 1; c < columns.size(); ++c) m_ScriptInterface.SetPropertyInt(row, c, table->GetCellText(r, c)); } return data; } private: const DumpTable& operator=(const DumpTable&); }; bool SortByName(AbstractProfileTable* a, AbstractProfileTable* b) { return (a->GetName() < b->GetName()); } } void CProfileViewer::SaveToFile() { // Open the file, if necessary. If this method is called several times, // the profile results will be appended to the previous ones from the same // run. if (! m->outputStream.is_open()) { // Open the file. (It will be closed when the CProfileViewer // destructor is called.) OsPath path = psLogDir()/"profile.txt"; m->outputStream.open(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc); if (m->outputStream.fail()) { LOGERROR("Failed to open profile log file"); return; } else { LOGMESSAGERENDER("Profiler snapshot saved to '%s'", path.string8()); } } time_t t; time(&t); m->outputStream << "================================================================\n\n"; m->outputStream << "PS profiler snapshot - " << asctime(localtime(&t)); std::vector tables = m->rootTables; sort(tables.begin(), tables.end(), SortByName); for_each(tables.begin(), tables.end(), WriteTable(m->outputStream)); m->outputStream << "\n\n================================================================\n"; m->outputStream.flush(); } JS::Value CProfileViewer::SaveToJS(ScriptInterface& scriptInterface) { JSContext* cx = scriptInterface.GetContext(); JSAutoRequest rq(cx); JS::RootedValue root(cx); scriptInterface.Eval("({})", &root); std::vector tables = m->rootTables; sort(tables.begin(), tables.end(), SortByName); for_each(tables.begin(), tables.end(), DumpTable(scriptInterface, root)); return root; } void CProfileViewer::ShowTable(const CStr& table) { m->path.clear(); if (table.length() > 0) { for (size_t i = 0; i < m->rootTables.size(); ++i) { if (m->rootTables[i]->GetName() == table) { m->path.push_back(m->rootTables[i]); m->profileVisible = true; return; } } } // No matching table found, so don't display anything m->profileVisible = false; } Index: ps/trunk/source/ps/XML/Xeromyces.cpp =================================================================== --- ps/trunk/source/ps/XML/Xeromyces.cpp (revision 16887) +++ ps/trunk/source/ps/XML/Xeromyces.cpp (revision 16888) @@ -1,421 +1,421 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include #include #include #include #include #include "maths/MD5.h" #include "ps/CacheLoader.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "RelaxNG.h" #include "Xeromyces.h" #include static CMutex g_ValidatorCacheLock; static std::map g_ValidatorCache; static bool g_XeromycesStarted = false; static void errorHandler(void* UNUSED(userData), xmlErrorPtr error) { // Strip a trailing newline std::string message = error->message; if (message.length() > 0 && message[message.length()-1] == '\n') message.erase(message.length()-1); LOGERROR("CXeromyces: Parse %s: %s:%d: %s", error->level == XML_ERR_WARNING ? "warning" : "error", error->file, error->line, message); // TODO: The (non-fatal) warnings and errors don't get stored in the XMB, // so the caching is less transparent than it should be } void CXeromyces::Startup() { ENSURE(!g_XeromycesStarted); xmlInitParser(); xmlSetStructuredErrorFunc(NULL, &errorHandler); CScopeLock lock(g_ValidatorCacheLock); g_ValidatorCache.insert(std::make_pair("", RelaxNGValidator())); g_XeromycesStarted = true; } void CXeromyces::Terminate() { ENSURE(g_XeromycesStarted); g_XeromycesStarted = false; ClearSchemaCache(); CScopeLock lock(g_ValidatorCacheLock); g_ValidatorCache.clear(); xmlSetStructuredErrorFunc(NULL, NULL); xmlCleanupParser(); } bool CXeromyces::AddValidator(const PIVFS& vfs, const std::string& name, const VfsPath& grammarPath) { ENSURE(g_XeromycesStarted); RelaxNGValidator validator; if (!validator.LoadGrammarFile(vfs, grammarPath)) { LOGERROR("CXeromyces: failed adding validator for '%s'", grammarPath.string8()); return false; } { CScopeLock lock(g_ValidatorCacheLock); std::map::iterator it = g_ValidatorCache.find(name); if (it != g_ValidatorCache.end()) g_ValidatorCache.erase(it); g_ValidatorCache.insert(std::make_pair(name, validator)); } return true; } bool CXeromyces::ValidateEncoded(const std::string& name, const std::wstring& filename, const std::string& document) { CScopeLock lock(g_ValidatorCacheLock); return GetValidator(name).ValidateEncoded(filename, document); } /** * NOTE: Callers MUST acquire the g_ValidatorCacheLock before calling this. */ RelaxNGValidator& CXeromyces::GetValidator(const std::string& name) { if (g_ValidatorCache.find(name) == g_ValidatorCache.end()) return g_ValidatorCache.find("")->second; return g_ValidatorCache.find(name)->second; } PSRETURN CXeromyces::Load(const PIVFS& vfs, const VfsPath& filename, const std::string& validatorName /* = "" */) { ENSURE(g_XeromycesStarted); CCacheLoader cacheLoader(vfs, L".xmb"); MD5 validatorGrammarHash; { CScopeLock lock(g_ValidatorCacheLock); validatorGrammarHash = GetValidator(validatorName).GetGrammarHash(); } VfsPath xmbPath; Status ret = cacheLoader.TryLoadingCached(filename, validatorGrammarHash, XMBVersion, xmbPath); if (ret == INFO::OK) { // Found a cached XMB - load it if (ReadXMBFile(vfs, xmbPath)) return PSRETURN_OK; // If this fails then we'll continue and (re)create the loose cache - // this failure legitimately happens due to partially-written XMB files. } else if (ret == INFO::SKIPPED) { // No cached version was found - we'll need to create it } else { ENSURE(ret < 0); // No source file or archive cache was found, so we can't load the // XML file at all LOGERROR("CCacheLoader failed to find archived or source file for: \"%s\"", filename.string8()); return PSRETURN_Xeromyces_XMLOpenFailed; } // XMB isn't up to date with the XML, so rebuild it return ConvertFile(vfs, filename, xmbPath, validatorName); } bool CXeromyces::GenerateCachedXMB(const PIVFS& vfs, const VfsPath& sourcePath, VfsPath& archiveCachePath, const std::string& validatorName /* = "" */) { CCacheLoader cacheLoader(vfs, L".xmb"); archiveCachePath = cacheLoader.ArchiveCachePath(sourcePath); return (ConvertFile(vfs, sourcePath, VfsPath("cache") / archiveCachePath, validatorName) == PSRETURN_OK); } PSRETURN CXeromyces::ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath, const std::string& validatorName) { CVFSFile input; if (input.Load(vfs, filename)) { LOGERROR("CXeromyces: Failed to open XML file %s", filename.string8()); return PSRETURN_Xeromyces_XMLOpenFailed; } xmlDocPtr doc = xmlReadMemory((const char*)input.GetBuffer(), input.GetBufferSize(), CStrW(filename.string()).ToUTF8().c_str(), NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA); if (!doc) { LOGERROR("CXeromyces: Failed to parse XML file %s", filename.string8()); return PSRETURN_Xeromyces_XMLParseError; } { CScopeLock lock(g_ValidatorCacheLock); RelaxNGValidator& validator = GetValidator(validatorName); if (validator.CanValidate() && !validator.ValidateEncoded(doc)) // For now, log the error and continue, in the future we might fail LOGERROR("CXeromyces: failed to validate XML file %s", filename.string8()); } WriteBuffer writeBuffer; CreateXMB(doc, writeBuffer); xmlFreeDoc(doc); // Save the file to disk, so it can be loaded quickly next time vfs->CreateFile(xmbPath, writeBuffer.Data(), writeBuffer.Size()); m_XMBBuffer = writeBuffer.Data(); // add a reference // Set up the XMBFile const bool ok = Initialise((const char*)m_XMBBuffer.get()); ENSURE(ok); return PSRETURN_OK; } bool CXeromyces::ReadXMBFile(const PIVFS& vfs, const VfsPath& filename) { size_t size; if(vfs->LoadFile(filename, m_XMBBuffer, size) < 0) return false; // if the game crashes during loading, (e.g. due to driver bugs), // it sometimes leaves empty XMB files in the cache. // reporting failure will cause our caller to re-generate the XMB. if(size == 0) return false; ENSURE(size >= 4); // make sure it's at least got the initial header // Set up the XMBFile if(!Initialise((const char*)m_XMBBuffer.get())) return false; return true; } PSRETURN CXeromyces::LoadString(const char* xml, const std::string& validatorName /* = "" */) { ENSURE(g_XeromycesStarted); xmlDocPtr doc = xmlReadMemory(xml, (int)strlen(xml), "(no file)", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA); if (!doc) { LOGERROR("CXeromyces: Failed to parse XML string"); return PSRETURN_Xeromyces_XMLParseError; } { CScopeLock lock(g_ValidatorCacheLock); RelaxNGValidator& validator = GetValidator(validatorName); if (validator.CanValidate() && !validator.ValidateEncoded(doc)) // For now, log the error and continue, in the future we might fail LOGERROR("CXeromyces: failed to validate XML string"); } WriteBuffer writeBuffer; CreateXMB(doc, writeBuffer); xmlFreeDoc(doc); m_XMBBuffer = writeBuffer.Data(); // add a reference // Set up the XMBFile const bool ok = Initialise((const char*)m_XMBBuffer.get()); ENSURE(ok); return PSRETURN_OK; } static void FindNames(const xmlNodePtr node, std::set& elementNames, std::set& attributeNames) { elementNames.insert((const char*)node->name); for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) attributeNames.insert((const char*)attr->name); for (xmlNodePtr child = node->children; child; child = child->next) if (child->type == XML_ELEMENT_NODE) FindNames(child, elementNames, attributeNames); } static void OutputElement(const xmlNodePtr node, WriteBuffer& writeBuffer, std::map& elementIDs, std::map& attributeIDs ) { // Filled in later with the length of the element size_t posLength = writeBuffer.Size(); writeBuffer.Append("????", 4); writeBuffer.Append(&elementIDs[(const char*)node->name], 4); u32 attrCount = 0; for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) ++attrCount; writeBuffer.Append(&attrCount, 4); u32 childCount = 0; for (xmlNodePtr child = node->children; child; child = child->next) if (child->type == XML_ELEMENT_NODE) ++childCount; writeBuffer.Append(&childCount, 4); // Filled in later with the offset to the list of child elements size_t posChildrenOffset = writeBuffer.Size(); writeBuffer.Append("????", 4); // Trim excess whitespace in the entity's text, while counting // the number of newlines trimmed (so that JS error reporting // can give the correct line number within the script) std::string whitespace = " \t\r\n"; std::string text; for (xmlNodePtr child = node->children; child; child = child->next) { if (child->type == XML_TEXT_NODE) { xmlChar* content = xmlNodeGetContent(child); text += std::string((const char*)content); xmlFree(content); } } u32 linenum = xmlGetLineNo(node); // Find the start of the non-whitespace section size_t first = text.find_first_not_of(whitespace); if (first == text.npos) // Entirely whitespace - easy to handle text = ""; else { // Count the number of \n being cut off, // and add them to the line number std::string trimmed (text.begin(), text.begin()+first); linenum += std::count(trimmed.begin(), trimmed.end(), '\n'); // Find the end of the non-whitespace section, // and trim off everything else size_t last = text.find_last_not_of(whitespace); text = text.substr(first, 1+last-first); } // Output text, prefixed by length in bytes if (text.length() == 0) { // No text; don't write much writeBuffer.Append("\0\0\0\0", 4); } else { // Write length and line number and null-terminated text u32 nodeLen = u32(4 + text.length()+1); writeBuffer.Append(&nodeLen, 4); writeBuffer.Append(&linenum, 4); writeBuffer.Append((void*)text.c_str(), nodeLen-4); } // Output attributes for (xmlAttrPtr attr = node->properties; attr; attr = attr->next) { writeBuffer.Append(&attributeIDs[(const char*)attr->name], 4); xmlChar* value = xmlNodeGetContent(attr->children); u32 attrLen = u32(xmlStrlen(value)+1); writeBuffer.Append(&attrLen, 4); writeBuffer.Append((void*)value, attrLen); xmlFree(value); } // Go back and fill in the child-element offset u32 childrenOffset = (u32)(writeBuffer.Size() - (posChildrenOffset+4)); writeBuffer.Overwrite(&childrenOffset, 4, posChildrenOffset); // Output all child elements for (xmlNodePtr child = node->children; child; child = child->next) if (child->type == XML_ELEMENT_NODE) OutputElement(child, writeBuffer, elementIDs, attributeIDs); // Go back and fill in the length u32 length = (u32)(writeBuffer.Size() - posLength); writeBuffer.Overwrite(&length, 4, posLength); } PSRETURN CXeromyces::CreateXMB(const xmlDocPtr doc, WriteBuffer& writeBuffer) { // Header writeBuffer.Append(UnfinishedHeaderMagicStr, 4); // Version writeBuffer.Append(&XMBVersion, 4); std::set::iterator it; u32 i; // Find the unique element/attribute names std::set elementNames; std::set attributeNames; FindNames(xmlDocGetRootElement(doc), elementNames, attributeNames); std::map elementIDs; std::map attributeIDs; // Output element names i = 0; u32 elementCount = (u32)elementNames.size(); writeBuffer.Append(&elementCount, 4); - for (it = elementNames.begin(); it != elementNames.end(); ++it) + for (const std::string& n : elementNames) { - u32 textLen = (u32)it->length()+1; + u32 textLen = (u32)n.length()+1; writeBuffer.Append(&textLen, 4); - writeBuffer.Append((void*)it->c_str(), textLen); - elementIDs[*it] = i++; + writeBuffer.Append((void*)n.c_str(), textLen); + elementIDs[n] = i++; } // Output attribute names i = 0; u32 attributeCount = (u32)attributeNames.size(); writeBuffer.Append(&attributeCount, 4); - for (it = attributeNames.begin(); it != attributeNames.end(); ++it) + for (const std::string& n : attributeNames) { - u32 textLen = (u32)it->length()+1; + u32 textLen = (u32)n.length()+1; writeBuffer.Append(&textLen, 4); - writeBuffer.Append((void*)it->c_str(), textLen); - attributeIDs[*it] = i++; + writeBuffer.Append((void*)n.c_str(), textLen); + attributeIDs[n] = i++; } OutputElement(xmlDocGetRootElement(doc), writeBuffer, elementIDs, attributeIDs); // file is now valid, so insert correct magic string writeBuffer.Overwrite(HeaderMagicStr, 4, 0); return PSRETURN_OK; } Index: ps/trunk/source/soundmanager/SoundManager.cpp =================================================================== --- ps/trunk/source/soundmanager/SoundManager.cpp (revision 16887) +++ ps/trunk/source/soundmanager/SoundManager.cpp (revision 16888) @@ -1,816 +1,816 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ISoundManager.h" #include "SoundManager.h" #include "data/SoundData.h" #include "items/CBufferItem.h" #include "items/CSoundItem.h" #include "items/CStreamItem.h" #include "lib/external_libraries/libsdl.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Profiler2.h" #include "ps/XML/Xeromyces.h" ISoundManager* g_SoundManager = NULL; #define SOURCE_NUM 64 #if CONFIG2_AUDIO class CSoundManagerWorker { NONCOPYABLE(CSoundManagerWorker); public: CSoundManagerWorker() { m_Items = new ItemsList; m_DeadItems = new ItemsList; m_Shutdown = false; int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this); ENSURE(ret == 0); } ~CSoundManagerWorker() { delete m_Items; CleanupItems(); delete m_DeadItems; } bool Shutdown() { { CScopeLock lock(m_WorkerMutex); m_Shutdown = true; ItemsList::iterator lstr = m_Items->begin(); while (lstr != m_Items->end()) { delete *lstr; ++lstr; } } pthread_join(m_WorkerThread, NULL); return true; } void addItem(ISoundItem* anItem) { CScopeLock lock(m_WorkerMutex); m_Items->push_back(anItem); } void CleanupItems() { CScopeLock lock(m_DeadItemsMutex); AL_CHECK; ItemsList::iterator deadItems = m_DeadItems->begin(); while (deadItems != m_DeadItems->end()) { delete *deadItems; ++deadItems; AL_CHECK; } m_DeadItems->clear(); } private: static void* RunThread(void* data) { debug_SetThreadName("CSoundManagerWorker"); g_Profiler2.RegisterCurrentThread("soundmanager"); static_cast(data)->Run(); return NULL; } void Run() { while (true) { // Handle shutdown requests as soon as possible if (GetShutdown()) return; int pauseTime = 500; if (g_SoundManager->InDistress()) pauseTime = 50; { CScopeLock lock(m_WorkerMutex); ItemsList::iterator lstr = m_Items->begin(); ItemsList* nextItemList = new ItemsList; while (lstr != m_Items->end()) { AL_CHECK; if ((*lstr)->IdleTask()) { if ((pauseTime == 500) && (*lstr)->IsFading()) pauseTime = 100; nextItemList->push_back(*lstr); } else { CScopeLock lock(m_DeadItemsMutex); m_DeadItems->push_back(*lstr); } ++lstr; AL_CHECK; } delete m_Items; m_Items = nextItemList; AL_CHECK; } SDL_Delay(pauseTime); } } bool GetShutdown() { CScopeLock lock(m_WorkerMutex); return m_Shutdown; } private: // Thread-related members: pthread_t m_WorkerThread; CMutex m_WorkerMutex; CMutex m_DeadItemsMutex; // Shared by main thread and worker thread: // These variables are all protected by a mutexes ItemsList* m_Items; ItemsList* m_DeadItems; bool m_Shutdown; CSoundManagerWorker(ISoundManager* UNUSED(other)){}; }; void ISoundManager::CreateSoundManager() { if (!g_SoundManager) { g_SoundManager = new CSoundManager(); g_SoundManager->StartWorker(); } } void ISoundManager::SetEnabled(bool doEnable) { if (g_SoundManager && !doEnable) SAFE_DELETE(g_SoundManager); else if (!g_SoundManager && doEnable) ISoundManager::CreateSoundManager(); } void ISoundManager::CloseGame() { if (CSoundManager* aSndMgr = (CSoundManager*)g_SoundManager) aSndMgr->SetAmbientItem(NULL); } void CSoundManager::al_ReportError(ALenum err, const char* caller, int line) { LOGERROR("OpenAL error: %s; called from %s (line %d)\n", alGetString(err), caller, line); } void CSoundManager::al_check(const char* caller, int line) { ALenum err = alGetError(); if (err != AL_NO_ERROR) al_ReportError(err, caller, line); } Status CSoundManager::ReloadChangedFiles(const VfsPath& UNUSED(path)) { // TODO implement sound file hotloading return INFO::OK; } /*static*/ Status CSoundManager::ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFiles(path); } CSoundManager::CSoundManager() : m_Context(nullptr), m_Device(nullptr), m_ALSourceBuffer(nullptr), m_CurrentTune(nullptr), m_CurrentEnvirons(nullptr), m_Worker(nullptr), m_DistressMutex(), m_PlayListItems(nullptr), m_SoundGroups(), m_Gain(.5f), m_MusicGain(.5f), m_AmbientGain(.5f), m_ActionGain(.5f), m_UIGain(.5f), m_Enabled(false), m_BufferSize(98304), m_BufferCount(50), m_SoundEnabled(true), m_MusicEnabled(true), m_MusicPaused(false), m_AmbientPaused(false), m_ActionPaused(false), m_RunningPlaylist(false), m_PlayingPlaylist(false), m_LoopingPlaylist(false), m_PlaylistGap(0), m_DistressErrCount(0), m_DistressTime(0) { CFG_GET_VAL("sound.mastergain", m_Gain); CFG_GET_VAL("sound.musicgain", m_MusicGain); CFG_GET_VAL("sound.ambientgain", m_AmbientGain); CFG_GET_VAL("sound.actiongain", m_ActionGain); CFG_GET_VAL("sound.uigain", m_UIGain); AlcInit(); if (m_Enabled) { SetMasterGain(m_Gain); InitListener(); m_PlayListItems = new PlayList; } if (!CXeromyces::AddValidator(g_VFS, "sound_group", "audio/sound_group.rng")) LOGERROR("CSoundManager: failed to load grammar file 'audio/sound_group.rng'"); RegisterFileReloadFunc(ReloadChangedFileCB, this); } CSoundManager::~CSoundManager() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); if (m_Worker) { AL_CHECK; m_Worker->Shutdown(); AL_CHECK; m_Worker->CleanupItems(); AL_CHECK; delete m_Worker; } AL_CHECK; - for (std::map::iterator it = m_SoundGroups.begin(); it != m_SoundGroups.end(); ++it) - delete it->second; + for (const std::pair& p : m_SoundGroups) + delete p.second; m_SoundGroups.clear(); if (m_PlayListItems) delete m_PlayListItems; if (m_ALSourceBuffer != NULL) delete[] m_ALSourceBuffer; if (m_Context) alcDestroyContext(m_Context); if (m_Device) alcCloseDevice(m_Device); } void CSoundManager::StartWorker() { if (m_Enabled) m_Worker = new CSoundManagerWorker(); } Status CSoundManager::AlcInit() { Status ret = INFO::OK; m_Device = alcOpenDevice(NULL); if (m_Device) { ALCint attribs[] = {ALC_STEREO_SOURCES, 16, 0}; m_Context = alcCreateContext(m_Device, &attribs[0]); if (m_Context) { alcMakeContextCurrent(m_Context); m_ALSourceBuffer = new ALSourceHolder[SOURCE_NUM]; ALuint* sourceList = new ALuint[SOURCE_NUM]; alGenSources(SOURCE_NUM, sourceList); ALCenum err = alcGetError(m_Device); if (err == ALC_NO_ERROR) { for (int x = 0; x < SOURCE_NUM; x++) { m_ALSourceBuffer[x].ALSource = sourceList[x]; m_ALSourceBuffer[x].SourceItem = NULL; } m_Enabled = true; } else { LOGERROR("error in gensource = %d", err); } delete[] sourceList; } } // check if init succeeded. // some OpenAL implementations don't indicate failure here correctly; // we need to check if the device and context pointers are actually valid. ALCenum err = alcGetError(m_Device); const char* dev_name = (const char*)alcGetString(m_Device, ALC_DEVICE_SPECIFIER); if (err == ALC_NO_ERROR && m_Device && m_Context) debug_printf("Sound: AlcInit success, using %s\n", dev_name); else { LOGERROR("Sound: AlcInit failed, m_Device=%p m_Context=%p dev_name=%s err=%x\n", (void *)m_Device, (void *)m_Context, dev_name, err); // FIXME Hack to get around exclusive access to the sound device #if OS_UNIX ret = INFO::OK; #else ret = ERR::FAIL; #endif // !OS_UNIX } return ret; } bool CSoundManager::InDistress() { CScopeLock lock(m_DistressMutex); if (m_DistressTime == 0) return false; else if ((timer_Time() - m_DistressTime) > 10) { m_DistressTime = 0; // Coming out of distress mode m_DistressErrCount = 0; return false; } return true; } void CSoundManager::SetDistressThroughShortage() { CScopeLock lock(m_DistressMutex); // Going into distress for normal reasons m_DistressTime = timer_Time(); } void CSoundManager::SetDistressThroughError() { CScopeLock lock(m_DistressMutex); // Going into distress due to unknown error m_DistressTime = timer_Time(); m_DistressErrCount++; } ALuint CSoundManager::GetALSource(ISoundItem* anItem) { for (int x = 0; x < SOURCE_NUM; x++) { if (!m_ALSourceBuffer[x].SourceItem) { m_ALSourceBuffer[x].SourceItem = anItem; return m_ALSourceBuffer[x].ALSource; } } SetDistressThroughShortage(); return 0; } void CSoundManager::ReleaseALSource(ALuint theSource) { for (int x = 0; x < SOURCE_NUM; x++) { if (m_ALSourceBuffer[x].ALSource == theSource) { m_ALSourceBuffer[x].SourceItem = NULL; return; } } } long CSoundManager::GetBufferCount() { return m_BufferCount; } long CSoundManager::GetBufferSize() { return m_BufferSize; } void CSoundManager::AddPlayListItem(const VfsPath& itemPath) { if (m_Enabled) m_PlayListItems->push_back(itemPath); } void CSoundManager::ClearPlayListItems() { if (m_Enabled) { if (m_PlayingPlaylist) SetMusicItem(NULL); m_PlayingPlaylist = false; m_LoopingPlaylist = false; m_RunningPlaylist = false; m_PlayListItems->clear(); } } void CSoundManager::StartPlayList(bool doLoop) { if (m_Enabled && m_MusicEnabled) { if (m_PlayListItems->size() > 0) { m_PlayingPlaylist = true; m_LoopingPlaylist = doLoop; m_RunningPlaylist = false; ISoundItem* aSnd = LoadItem((m_PlayListItems->at(0))); if (aSnd) SetMusicItem(aSnd); else SetMusicItem(NULL); } } } void CSoundManager::SetMasterGain(float gain) { if (m_Enabled) { m_Gain = gain; alListenerf(AL_GAIN, m_Gain); AL_CHECK; } } void CSoundManager::SetMusicGain(float gain) { m_MusicGain = gain; } void CSoundManager::SetAmbientGain(float gain) { m_AmbientGain = gain; } void CSoundManager::SetActionGain(float gain) { m_ActionGain = gain; } void CSoundManager::SetUIGain(float gain) { m_UIGain = gain; } ISoundItem* CSoundManager::LoadItem(const VfsPath& itemPath) { AL_CHECK; if (m_Enabled) { CSoundData* itemData = CSoundData::SoundDataFromFile(itemPath); AL_CHECK; if (itemData) return CSoundManager::ItemForData(itemData); } return NULL; } ISoundItem* CSoundManager::ItemForData(CSoundData* itemData) { AL_CHECK; ISoundItem* answer = NULL; AL_CHECK; if (m_Enabled && (itemData != NULL)) { if (itemData->IsOneShot()) { if (itemData->GetBufferCount() == 1) answer = new CSoundItem(itemData); else answer = new CBufferItem(itemData); } else { answer = new CStreamItem(itemData); } if (answer && m_Worker) m_Worker->addItem(answer); } return answer; } void CSoundManager::IdleTask() { if (m_Enabled) { if (m_CurrentTune) { m_CurrentTune->EnsurePlay(); if (m_PlayingPlaylist && m_RunningPlaylist) { if (m_CurrentTune->Finished()) { if (m_PlaylistGap == 0) { m_PlaylistGap = timer_Time() + 15; } else if (m_PlaylistGap < timer_Time()) { m_PlaylistGap = 0; PlayList::iterator it = find(m_PlayListItems->begin(), m_PlayListItems->end(), m_CurrentTune->GetName()); if (it != m_PlayListItems->end()) { ++it; Path nextPath; if (it == m_PlayListItems->end()) nextPath = m_PlayListItems->at(0); else nextPath = *it; ISoundItem* aSnd = LoadItem(nextPath); if (aSnd) SetMusicItem(aSnd); } } } } } if (m_CurrentEnvirons) m_CurrentEnvirons->EnsurePlay(); if (m_Worker) m_Worker->CleanupItems(); } } ISoundItem* CSoundManager::ItemForEntity(entity_id_t UNUSED(source), CSoundData* sndData) { ISoundItem* currentItem = NULL; if (m_Enabled) currentItem = ItemForData(sndData); return currentItem; } void CSoundManager::InitListener() { ALfloat listenerPos[] = {0.0, 0.0, 0.0}; ALfloat listenerVel[] = {0.0, 0.0, 0.0}; ALfloat listenerOri[] = {0.0, 0.0, -1.0, 0.0, 1.0, 0.0}; alListenerfv(AL_POSITION, listenerPos); alListenerfv(AL_VELOCITY, listenerVel); alListenerfv(AL_ORIENTATION, listenerOri); alDistanceModel(AL_LINEAR_DISTANCE); } void CSoundManager::PlayGroupItem(ISoundItem* anItem, ALfloat groupGain) { if (anItem) { if (m_Enabled && (m_ActionGain > 0)) { anItem->SetGain(m_ActionGain * groupGain); anItem->PlayAndDelete(); AL_CHECK; } } } void CSoundManager::SetMusicEnabled(bool isEnabled) { if (m_CurrentTune && !isEnabled) { m_CurrentTune->FadeAndDelete(1.00); m_CurrentTune = NULL; } m_MusicEnabled = isEnabled; } void CSoundManager::PlayAsGroup(const VfsPath& groupPath, CVector3D sourcePos, entity_id_t source, bool ownedSound) { // Make sure the sound group is loaded CSoundGroup* group; if (m_SoundGroups.find(groupPath.string()) == m_SoundGroups.end()) { group = new CSoundGroup(); if (!group->LoadSoundGroup(L"audio/" + groupPath.string())) { LOGERROR("Failed to load sound group '%s'", groupPath.string8()); delete group; group = NULL; } // Cache the sound group (or the null, if it failed) m_SoundGroups[groupPath.string()] = group; } else { group = m_SoundGroups[groupPath.string()]; } // Failed to load group -> do nothing if (group && (ownedSound || !group->TestFlag(eOwnerOnly))) group->PlayNext(sourcePos, source); } void CSoundManager::PlayAsMusic(const VfsPath& itemPath, bool looping) { if (m_Enabled) { UNUSED2(looping); ISoundItem* aSnd = LoadItem(itemPath); if (aSnd != NULL) SetMusicItem(aSnd); } } void CSoundManager::PlayAsAmbient(const VfsPath& itemPath, bool looping) { if (m_Enabled) { UNUSED2(looping); ISoundItem* aSnd = LoadItem(itemPath); if (aSnd != NULL) SetAmbientItem(aSnd); } } void CSoundManager::PlayAsUI(const VfsPath& itemPath, bool looping) { if (m_Enabled) { IdleTask(); if (ISoundItem* anItem = LoadItem(itemPath)) { if (m_UIGain > 0) { anItem->SetGain(m_UIGain); anItem->SetLooping(looping); anItem->PlayAndDelete(); } } AL_CHECK; } } void CSoundManager::Pause(bool pauseIt) { PauseMusic(pauseIt); PauseAmbient(pauseIt); PauseAction(pauseIt); } void CSoundManager::PauseMusic(bool pauseIt) { if (m_CurrentTune && pauseIt && !m_MusicPaused) { m_CurrentTune->FadeAndPause(1.0); } else if (m_CurrentTune && m_MusicPaused && !pauseIt && m_MusicEnabled) { m_CurrentTune->SetGain(0); m_CurrentTune->Resume(); m_CurrentTune->FadeToIn(m_MusicGain, 1.0); } m_MusicPaused = pauseIt; } void CSoundManager::PauseAmbient(bool pauseIt) { if (m_CurrentEnvirons && pauseIt) m_CurrentEnvirons->Pause(); else if (m_CurrentEnvirons) m_CurrentEnvirons->Resume(); m_AmbientPaused = pauseIt; } void CSoundManager::PauseAction(bool pauseIt) { m_ActionPaused = pauseIt; } void CSoundManager::SetMusicItem(ISoundItem* anItem) { if (m_Enabled) { AL_CHECK; if (m_CurrentTune) { m_CurrentTune->FadeAndDelete(2.00); m_CurrentTune = NULL; } IdleTask(); if (anItem) { if (m_MusicEnabled) { m_CurrentTune = anItem; m_CurrentTune->SetGain(0); if (m_PlayingPlaylist) { m_RunningPlaylist = true; m_CurrentTune->Play(); } else m_CurrentTune->PlayLoop(); m_MusicPaused = false; m_CurrentTune->FadeToIn(m_MusicGain, 1.00); } else { anItem->StopAndDelete(); } } AL_CHECK; } } void CSoundManager::SetAmbientItem(ISoundItem* anItem) { if (m_Enabled) { if (m_CurrentEnvirons) { m_CurrentEnvirons->FadeAndDelete(3.00); m_CurrentEnvirons = NULL; } IdleTask(); if (anItem) { if (m_AmbientGain > 0) { m_CurrentEnvirons = anItem; m_CurrentEnvirons->SetGain(0); m_CurrentEnvirons->PlayLoop(); m_CurrentEnvirons->FadeToIn(m_AmbientGain, 2.00); } } AL_CHECK; } } #else // CONFIG2_AUDIO void ISoundManager::CreateSoundManager(){} void ISoundManager::SetEnabled(bool UNUSED(doEnable)){} void ISoundManager::CloseGame(){} #endif // CONFIG2_AUDIO