- Updated UFA:Plugin (mattn2)
[xonotic/netradiant.git] / plugins / entity / skincache.cpp
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 #include "skincache.h"
23
24 #include "ifilesystem.h"
25 #include "iscriplib.h"
26 #include "iarchive.h"
27 #include "modelskin.h"
28
29 #include <map>
30
31 #include "stream/stringstream.h"
32 #include "generic/callback.h"
33 #include "container/cache.h"
34 #include "container/hashfunc.h"
35 #include "os/path.h"
36 #include "moduleobservers.h"
37 #include "modulesystem/singletonmodule.h"
38 #include "stringio.h"
39
40 void parseShaderName(CopiedString& name, const char* token)
41 {
42   StringOutputStream cleaned(256);
43   cleaned << PathCleaned(token);
44   name = cleaned.c_str();
45 }
46
47 class Doom3ModelSkin
48 {
49   typedef std::map<CopiedString, CopiedString> Remaps;
50   Remaps m_remaps;
51 public:
52   bool parseTokens(Tokeniser& tokeniser)
53   {
54     RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "{"));
55     tokeniser.nextLine();
56     for(;;)
57     {
58       const char* token = tokeniser.getToken();
59       if(token == 0)
60       {
61         return false;
62       }
63       if(string_equal(token, "}"))
64       {
65         tokeniser.nextLine();
66         return true;
67       }
68       else if(string_equal(token, "model"))
69       {
70         //const char* model =
71         tokeniser.getToken();
72       }
73       else
74       {
75         CopiedString from, to;
76         parseShaderName(from, token);
77
78         tokeniser.nextLine(); // hack to handle badly formed skins
79
80         parseShaderName(to, tokeniser.getToken());
81
82         if(!string_equal(from.c_str(), to.c_str()))
83         {
84           m_remaps.insert(Remaps::value_type(from, to));
85         }
86       }
87       tokeniser.nextLine();
88     }
89   }
90   const char* getRemap(const char* name) const
91   {
92     Remaps::const_iterator i = m_remaps.find(name);
93     if(i != m_remaps.end())
94     {
95       return (*i).second.c_str();
96     }
97     return "";
98   }
99   void forEachRemap(const SkinRemapCallback& callback) const
100   {
101     for(Remaps::const_iterator i = m_remaps.begin(); i != m_remaps.end(); ++i)
102     {
103       callback(SkinRemap((*i).first.c_str(), (*i).second.c_str()));
104     }
105   }
106 };
107
108 class GlobalSkins
109 {
110 public:
111   typedef std::map<CopiedString, Doom3ModelSkin> SkinMap;
112   SkinMap m_skins;
113   Doom3ModelSkin g_nullSkin;
114
115   Doom3ModelSkin& getSkin(const char* name)
116   {
117     SkinMap::iterator i = m_skins.find(name);
118     if(i != m_skins.end())
119     {
120       return (*i).second;
121     }
122     
123     return g_nullSkin;
124   }
125
126   bool parseTokens(Tokeniser& tokeniser)
127   {
128     tokeniser.nextLine();
129     for(;;)
130     {
131       const char* token = tokeniser.getToken();
132       if(token == 0)
133       {
134         // end of token stream
135         return true;
136       }
137       if(!string_equal(token, "skin"))
138       {
139         Tokeniser_unexpectedError(tokeniser, token, "skin");
140         return false;
141       }
142       const char* other = tokeniser.getToken();
143       if(other == 0)
144       {
145         Tokeniser_unexpectedError(tokeniser, token, "#string");
146         return false;
147       }
148       CopiedString name;
149       parseShaderName(name, other);
150       Doom3ModelSkin& skin = m_skins[name];
151       RETURN_FALSE_IF_FAIL(skin.parseTokens(tokeniser));
152     }
153   }
154
155   void parseFile(const char* name)
156   {
157     StringOutputStream relativeName(64);
158     relativeName << "skins/" << name;
159     ArchiveTextFile* file = GlobalFileSystem().openTextFile(relativeName.c_str());
160     if(file != 0)
161     {
162       globalOutputStream() << "parsing skins from " << makeQuoted(name) << "\n";
163       {
164         Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(file->getInputStream());
165         parseTokens(tokeniser);
166         tokeniser.release();
167       }
168       file->release();
169     }
170     else
171     {
172       globalErrorStream() << "failed to open " << makeQuoted(name) << "\n";
173     }
174   }
175
176   typedef MemberCaller1<GlobalSkins, const char*, &GlobalSkins::parseFile> ParseFileCaller;
177
178   void construct()
179   {
180     GlobalFileSystem().forEachFile("skins/", "skin", ParseFileCaller(*this));
181   }
182
183   void destroy()
184   {
185     m_skins.clear();
186   }
187
188   void realise()
189   {
190     construct();
191   }
192   void unrealise()
193   {
194     destroy();
195   }
196 };
197
198 GlobalSkins g_skins;
199
200
201 class Doom3ModelSkinCacheElement : public ModelSkin
202 {
203   ModuleObservers m_observers;
204   Doom3ModelSkin* m_skin;
205 public:
206   Doom3ModelSkinCacheElement() : m_skin(0)
207   {
208   }
209   void attach(ModuleObserver& observer)
210   {
211     m_observers.attach(observer);
212     if(realised())
213     {
214       observer.realise();
215     }
216   }
217   void detach(ModuleObserver& observer)
218   {
219     if(realised())
220     {
221       observer.unrealise();
222     }
223     m_observers.detach(observer);
224   }
225   bool realised() const
226   {
227     return m_skin != 0;
228   }
229   void realise(const char* name)
230   {
231     ASSERT_MESSAGE(!realised(), "Doom3ModelSkinCacheElement::realise: already realised");
232     m_skin = &g_skins.getSkin(name);
233     m_observers.realise();
234   }
235   void unrealise()
236   {
237     ASSERT_MESSAGE(realised(), "Doom3ModelSkinCacheElement::unrealise: not realised");
238     m_observers.unrealise();
239     m_skin = 0;
240   }
241   const char* getRemap(const char* name) const
242   {
243     ASSERT_MESSAGE(realised(), "Doom3ModelSkinCacheElement::getRemap: not realised");
244     return m_skin->getRemap(name);
245   }
246   void forEachRemap(const SkinRemapCallback& callback) const
247   {
248     ASSERT_MESSAGE(realised(), "Doom3ModelSkinCacheElement::forEachRemap: not realised");
249     m_skin->forEachRemap(callback);
250   }
251 };
252
253 class Doom3ModelSkinCache : public ModelSkinCache, public ModuleObserver
254 {
255   class CreateDoom3ModelSkin
256   {
257     Doom3ModelSkinCache& m_cache;
258   public:
259     explicit CreateDoom3ModelSkin(Doom3ModelSkinCache& cache)
260       : m_cache(cache)
261     {
262     }
263     Doom3ModelSkinCacheElement* construct(const CopiedString& name)
264     {
265       Doom3ModelSkinCacheElement* skin = new Doom3ModelSkinCacheElement;
266       if(m_cache.realised())
267       {
268         skin->realise(name.c_str());
269       }
270       return skin;
271     }
272     void destroy(Doom3ModelSkinCacheElement* skin)
273     {
274       if(m_cache.realised())
275       {
276         skin->unrealise();
277       }
278       delete skin;
279     }
280   };
281
282   typedef HashedCache<CopiedString, Doom3ModelSkinCacheElement, HashString, std::equal_to<CopiedString>, CreateDoom3ModelSkin> Cache;
283   Cache m_cache;
284   bool m_realised;
285
286 public:
287   typedef ModelSkinCache Type;
288   STRING_CONSTANT(Name, "*");
289   ModelSkinCache* getTable()
290   {
291     return this;
292   }
293
294   Doom3ModelSkinCache() : m_cache(CreateDoom3ModelSkin(*this)), m_realised(false)
295   {
296     GlobalFileSystem().attach(*this);
297   }
298   ~Doom3ModelSkinCache()
299   {
300     GlobalFileSystem().detach(*this);
301   }
302
303   ModelSkin& capture(const char* name)
304   {
305     return *m_cache.capture(name);
306   }
307   void release(const char* name)
308   {
309     m_cache.release(name);
310   }
311
312   bool realised() const
313   {
314     return m_realised;
315   }
316   void realise()
317   {
318     g_skins.realise();
319     m_realised = true;
320     for(Cache::iterator i = m_cache.begin(); i != m_cache.end(); ++i)
321     {
322       (*i).value->realise((*i).key.c_str());
323     }
324   }
325   void unrealise()
326   {
327     m_realised = false;
328     for(Cache::iterator i = m_cache.begin(); i != m_cache.end(); ++i)
329     {
330       (*i).value->unrealise();
331     }
332     g_skins.unrealise();
333   }
334 };
335
336 class Doom3ModelSkinCacheDependencies : public GlobalFileSystemModuleRef, public GlobalScripLibModuleRef
337 {
338 };
339
340 typedef SingletonModule<Doom3ModelSkinCache, Doom3ModelSkinCacheDependencies> Doom3ModelSkinCacheModule;
341
342 Doom3ModelSkinCacheModule g_Doom3ModelSkinCacheModule;
343
344 void Doom3ModelSkinCacheModule_selfRegister(ModuleServer& server)
345 {
346   g_Doom3ModelSkinCacheModule.selfRegister();
347 }
348