1 /***************************************************************************
2 * Mechanized Assault and Exploration Reloaded Projectfile *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
18 ***************************************************************************/
19
20 #include <algorithm>
21
22 #include "drawingcache.h"
23
24 #include "game/data/units/building.h"
25 #include "game/logic/client.h"
26 #include "loaddata.h"
27 #include "game/data/player/player.h"
28 #include "settings.h"
29 #include "game/data/units/vehicle.h"
30 #include "game/data/map/map.h"
31 #include "ui/graphical/game/animations/animationtimer.h"
32 #include "ui/graphical/framecounter.h"
33
sDrawingCacheEntry()34 sDrawingCacheEntry::sDrawingCacheEntry()
35 {}
36
sDrawingCacheEntry(sDrawingCacheEntry && other)37 sDrawingCacheEntry::sDrawingCacheEntry(sDrawingCacheEntry&& other) :
38 BaseN(other.BaseN),
39 BaseBN(other.BaseBN),
40 BaseE(other.BaseE),
41 BaseBE(other.BaseBE),
42 BaseS(other.BaseS),
43 BaseBS(other.BaseBS),
44 BaseW(other.BaseW),
45 BaseBW(other.BaseBW),
46 clan(other.clan),
47 frame(other.frame),
48 flightHigh(other.flightHigh),
49 big(other.big),
50 isBuilding(other.isBuilding),
51 isClearing(other.isClearing),
52 stealth(other.stealth),
53 water(other.water),
54 id(other.id),
55 owner(other.owner),
56 dir(other.dir),
57 zoom(other.zoom),
58 lastUsed(other.lastUsed),
59 surface(std::move(other.surface))
60 {}
61
operator =(sDrawingCacheEntry && other)62 sDrawingCacheEntry& sDrawingCacheEntry::operator=(sDrawingCacheEntry&& other)
63 {
64 BaseN = other.BaseN;
65 BaseBN = other.BaseBN;
66 BaseE = other.BaseE;
67 BaseBE = other.BaseBE;
68 BaseS = other.BaseS;
69 BaseBS = other.BaseBS;
70 BaseW = other.BaseW;
71 BaseBW = other.BaseBW;
72 clan = other.clan;
73
74 frame = other.frame;
75 flightHigh = other.flightHigh;
76 big = other.big;
77 isBuilding = other.isBuilding;
78 isClearing = other.isClearing;
79 stealth = other.stealth;
80 water = other.water;
81
82 id = other.id;
83 owner = other.owner;
84 dir = other.dir;
85 zoom = other.zoom;
86 lastUsed = other.lastUsed;
87
88 surface = std::move(other.surface);
89
90 return *this;
91 }
92
init(const cVehicle & vehicle,const cMap & map,const cPlayer * player,unsigned long long animationTime,double zoom_,unsigned long long frameNr)93 void sDrawingCacheEntry::init (const cVehicle& vehicle, const cMap& map, const cPlayer* player, unsigned long long animationTime, double zoom_, unsigned long long frameNr)
94 {
95 dir = vehicle.dir;
96 owner = vehicle.getOwner();
97 isBuilding = vehicle.isUnitBuildingABuilding();
98 isClearing = vehicle.isUnitClearing();
99 flightHigh = vehicle.getFlightHeight();
100 big = vehicle.data.isBig;
101 id = vehicle.data.ID;
102 if (vehicle.data.animationMovement)
103 frame = vehicle.WalkFrame;
104 else
105 frame = animationTime % 4;
106
107 water = map.isWaterOrCoast (vehicle.getPosition()) && !map.getField (vehicle.getPosition()).getBaseBuilding();
108
109 bool isOnWaterAndNotCoast = map.isWater (vehicle.getPosition());
110 //if the vehicle can also drive on land, we have to check, whether there is a brige, platform, etc.
111 //because the vehicle will drive on the bridge
112 cBuilding* building = map.getField (vehicle.getPosition()).getBaseBuilding();
113 if (vehicle.data.factorGround > 0 && building
114 && (building->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_SEA
115 || building->data.surfacePosition == sUnitData::SURFACE_POS_BASE
116 || building->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_BASE))
117 {
118 isOnWaterAndNotCoast = false;
119 }
120 if ((vehicle.data.isStealthOn & TERRAIN_SEA) && isOnWaterAndNotCoast && vehicle.detectedByPlayerList.empty() && vehicle.getOwner() == player)
121 stealth = true;
122 else
123 stealth = false;
124
125 zoom = zoom_;
126 lastUsed = frameNr;
127
128 //determine needed size of the surface
129 int height = (int) std::max (vehicle.uiData->img_org[vehicle.dir]->h * zoom, vehicle.uiData->shw_org[vehicle.dir]->h * zoom);
130 int width = (int) std::max (vehicle.uiData->img_org[vehicle.dir]->w * zoom, vehicle.uiData->shw_org[vehicle.dir]->w * zoom);
131 if (vehicle.getFlightHeight() > 0)
132 {
133 int shwOff = ((int) (Round (vehicle.uiData->img_org[vehicle.dir]->w * zoom) * (vehicle.getFlightHeight() / 64.0f)));
134 height += shwOff;
135 width += shwOff;
136 }
137 if (vehicle.isUnitClearing() || vehicle.isUnitBuildingABuilding())
138 {
139 width = 130;
140 height = 130;
141 }
142 surface = AutoSurface (SDL_CreateRGBSurface (0, width, height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));
143
144 SDL_FillRect (surface.get(), nullptr, SDL_MapRGBA (surface->format, 0, 0, 0, 0));
145 }
146
init(const cBuilding & building,double zoom_,unsigned long long frameNr)147 void sDrawingCacheEntry::init (const cBuilding& building, double zoom_, unsigned long long frameNr)
148 {
149 BaseN = building.BaseN;
150 BaseBN = building.BaseBN;
151 BaseE = building.BaseE;
152 BaseBE = building.BaseBE;
153 BaseS = building.BaseS;
154 BaseBS = building.BaseBS;
155 BaseW = building.BaseW;
156 BaseBW = building.BaseBW;
157 dir = building.dir;
158 owner = building.getOwner();
159 id = building.data.ID;
160 clan = building.getOwner()->getClan();
161
162 zoom = zoom_;
163 lastUsed = frameNr;
164
165 //determine needed size of the surface
166 int height = (int) std::max (building.uiData->img_org->h * zoom, building.uiData->shw_org->h * zoom);
167 int width = (int) std::max (building.uiData->img_org->w * zoom, building.uiData->shw_org->w * zoom);
168 if (building.data.hasFrames) width = (int) (building.uiData->shw_org->w * zoom);
169
170 surface = AutoSurface (SDL_CreateRGBSurface (0, width, height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000));
171
172 SDL_FillRect (surface.get(), nullptr, SDL_MapRGBA (surface->format, 0, 0, 0, 0));
173 }
174
cDrawingCache(std::shared_ptr<const cFrameCounter> frameCounter_)175 cDrawingCache::cDrawingCache (std::shared_ptr<const cFrameCounter> frameCounter_) :
176 frameCounter (frameCounter_),
177 player (nullptr)
178 {
179 //assert (animationTimer != nullptr);
180
181 cacheHits = 0;
182 cacheMisses = 0;
183 notCached = 0;
184 cacheSize = 0;
185 const std::size_t maxCacheSize = cSettings::getInstance().getCacheSize(); //set cache size from config
186 cachedImages.resize (maxCacheSize);
187 }
188
setPlayer(const cPlayer * player_)189 void cDrawingCache::setPlayer (const cPlayer* player_)
190 {
191 player = player_;
192 }
193
getCachedImage(const cBuilding & building,double zoom,unsigned long long animationTime)194 SDL_Surface* cDrawingCache::getCachedImage (const cBuilding& building, double zoom, unsigned long long animationTime)
195 {
196 if (!canCache (building)) return nullptr;
197
198 for (unsigned int i = 0; i < cacheSize; i++)
199 {
200 sDrawingCacheEntry& entry = cachedImages[i];
201
202 // check whether the entry's properties are equal to the building
203 if (entry.id != building.data.ID) continue;
204 if (entry.owner != building.getOwner()) continue;
205 if (building.SubBase)
206 {
207 if (building.BaseN != entry.BaseN ||
208 building.BaseE != entry.BaseE ||
209 building.BaseS != entry.BaseS ||
210 building.BaseW != entry.BaseW) continue;
211
212 if (building.data.isBig)
213 {
214 if (building.BaseBN != entry.BaseBN ||
215 building.BaseBE != entry.BaseBE ||
216 building.BaseBS != entry.BaseBS ||
217 building.BaseBW != entry.BaseBW) continue;
218 }
219
220 }
221 if (building.data.hasFrames && !building.data.isAnimated)
222 {
223 if (entry.dir != building.dir) continue;
224 }
225 if (entry.zoom != zoom) continue;
226
227 if (building.data.hasClanLogos && building.getOwner()->getClan() != entry.clan) continue;
228
229 //cache hit!
230 cacheHits++;
231 entry.lastUsed = frameCounter->getFrame();
232 return entry.surface.get();
233 }
234
235 //cache miss!
236 cacheMisses++;
237 return nullptr;
238 }
239
getCachedImage(const cVehicle & vehicle,double zoom,const cMap & map,unsigned long long animationTime)240 SDL_Surface* cDrawingCache::getCachedImage (const cVehicle& vehicle, double zoom, const cMap& map, unsigned long long animationTime)
241 {
242 if (!canCache (vehicle)) return nullptr;
243
244 for (unsigned int i = 0; i < cacheSize; i++)
245 {
246 sDrawingCacheEntry& entry = cachedImages[i];
247
248 // check whether the entry's properties are equal to the building
249 if (entry.id != vehicle.data.ID) continue;
250 if (entry.owner != vehicle.getOwner()) continue;
251 if (entry.big != vehicle.data.isBig) continue;
252 if (entry.isBuilding != vehicle.isUnitBuildingABuilding()) continue;
253 if (entry.isClearing != vehicle.isUnitClearing()) continue;
254
255 if (entry.flightHigh != vehicle.getFlightHeight()) continue;
256 if (entry.dir != vehicle.dir) continue;
257
258 if (vehicle.data.animationMovement)
259 {
260 if (entry.frame != vehicle.WalkFrame) continue;
261 }
262
263 if (vehicle.isUnitBuildingABuilding() || vehicle.isUnitClearing())
264 {
265 if (entry.frame != animationTime % 4) continue;
266 }
267
268 if (entry.zoom != zoom) continue;
269
270 bool water = map.isWaterOrCoast (vehicle.getPosition()) && !map.getField (vehicle.getPosition()).getBaseBuilding();
271 if (vehicle.isUnitBuildingABuilding())
272 {
273 if (water != entry.water) continue;
274 }
275
276 //check the stealth flag
277 bool stealth = false;
278
279 bool isOnWaterAndNotCoast = map.isWater (vehicle.getPosition());
280 const cBuilding* building = map.getField (vehicle.getPosition()).getBaseBuilding();
281 if (vehicle.data.factorGround > 0 && building
282 && (building->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_SEA
283 || building->data.surfacePosition == sUnitData::SURFACE_POS_BASE
284 || building->data.surfacePosition == sUnitData::SURFACE_POS_ABOVE_BASE))
285 {
286 isOnWaterAndNotCoast = false;
287 }
288
289 if ((vehicle.data.isStealthOn & TERRAIN_SEA) && isOnWaterAndNotCoast && vehicle.detectedByPlayerList.empty() && vehicle.getOwner() == player)
290 stealth = true;
291
292 if (entry.stealth != stealth) continue;
293
294 //cache hit!
295 cacheHits++;
296 entry.lastUsed = frameCounter->getFrame();
297 return entry.surface.get();
298 }
299
300 //cache miss!
301
302 cacheMisses++;
303 return nullptr;
304 }
305
createNewEntry(const cBuilding & building,double zoom,unsigned long long animationTime)306 SDL_Surface* cDrawingCache::createNewEntry (const cBuilding& building, double zoom, unsigned long long animationTime)
307 {
308 if (!canCache (building)) return nullptr;
309
310 if (cacheSize < cachedImages.size()) //cache hasn't reached the max size, so allocate a new entry
311 {
312 sDrawingCacheEntry& entry = cachedImages[cacheSize];
313 cacheSize++;
314
315 //set properties of the cached image
316 entry.init (building, zoom, frameCounter->getFrame());
317
318 return entry.surface.get();
319 }
320
321 //try to find an old entry to reuse
322 int oldest = 0;
323 for (unsigned int i = 0; i < cacheSize; i++)
324 {
325 if (cachedImages[i].lastUsed < cachedImages[oldest].lastUsed)
326 oldest = i;
327 }
328
329 if (frameCounter->getFrame() - cachedImages[oldest].lastUsed > 5)
330 {
331
332 //entry has not been used for 5 frames. Use it for the new entry.
333 sDrawingCacheEntry& entry = cachedImages[oldest];
334
335 //set properties of the cached image
336 entry.init (building, zoom, frameCounter->getFrame());
337
338 return entry.surface.get();
339 }
340
341 //there are no old entries in the cache.
342 return nullptr;
343 }
344
createNewEntry(const cVehicle & vehicle,double zoom,const cMap & map,unsigned long long animationTime)345 SDL_Surface* cDrawingCache::createNewEntry (const cVehicle& vehicle, double zoom, const cMap& map, unsigned long long animationTime)
346 {
347 if (!canCache (vehicle))
348 return nullptr;
349
350 if (cacheSize < cachedImages.size()) //cache hasn't reached the max size, so allocate a new entry
351 {
352 sDrawingCacheEntry& entry = cachedImages[cacheSize];
353
354 //set properties of the cached image
355 entry.init (vehicle, map, player, animationTime, zoom, frameCounter->getFrame());
356
357 cacheSize++;
358 return entry.surface.get();
359 }
360
361 //try to find an old entry to reuse
362 int oldest = 0;
363 for (unsigned int i = 0; i < cacheSize; i++)
364 {
365 if (cachedImages[i].lastUsed < cachedImages[oldest].lastUsed)
366 oldest = i;
367 }
368
369 if (frameCounter->getFrame() - cachedImages[oldest].lastUsed > 5)
370 {
371
372 //entry has not been used for 5 frames. Use it for the new entry.
373 sDrawingCacheEntry& entry = cachedImages[oldest];
374
375 //set properties of the cached image
376 entry.init (vehicle, map, player, animationTime, zoom, frameCounter->getFrame());
377 return entry.surface.get();
378 }
379
380 //there are no old entries in the cache.
381 return nullptr;
382 }
383
flush()384 void cDrawingCache::flush()
385 {
386 cacheSize = 0;
387 }
388
canCache(const cBuilding & building)389 bool cDrawingCache::canCache (const cBuilding& building)
390 {
391 if (!building.getOwner() ||
392 building.alphaEffectValue ||
393 building.data.isAnimated)
394 {
395 notCached++;
396 return false;
397 }
398 return true;
399 }
400
canCache(const cVehicle & vehicle)401 bool cDrawingCache::canCache (const cVehicle& vehicle)
402 {
403 if ((vehicle.isUnitBuildingABuilding() || vehicle.isUnitClearing()) && vehicle.job)
404 {
405 notCached++;
406 return false;
407 }
408
409 if (vehicle.alphaEffectValue)
410 {
411 notCached++;
412 return false;
413 }
414
415 if (vehicle.getFlightHeight() > 0 && vehicle.getFlightHeight() < 64)
416 {
417 notCached++;
418 return false;
419 }
420
421 if (vehicle.isUnitBuildingABuilding() && vehicle.data.isBig && vehicle.bigBetonAlpha < 254)
422 {
423 notCached++;
424 return false;
425 }
426 return true;
427 }
428
resetStatistics()429 void cDrawingCache::resetStatistics()
430 {
431 cacheMisses = 0;
432 cacheHits = 0;
433 notCached = 0;
434 }
435
getMaxCacheSize() const436 int cDrawingCache::getMaxCacheSize() const
437 {
438 return cachedImages.size();
439 }
440
setMaxCacheSize(unsigned int newSize)441 void cDrawingCache::setMaxCacheSize (unsigned int newSize)
442 {
443 cachedImages.clear();
444 cachedImages.resize (newSize);
445 cacheSize = 0;
446
447 cSettings::getInstance().setCacheSize (newSize);
448 }
449
getCacheSize() const450 int cDrawingCache::getCacheSize() const
451 {
452 return cacheSize;
453 }
454
getCacheHits() const455 int cDrawingCache::getCacheHits() const
456 {
457 return cacheHits;
458 }
459
getCacheMisses() const460 int cDrawingCache::getCacheMisses() const
461 {
462 return cacheMisses;
463 }
464
getNotCached() const465 int cDrawingCache::getNotCached() const
466 {
467 return notCached / 2;
468 }
469