]> www.infradead.org Git - users/mchehab/andors-trail.git/commitdiff
Added LruCache for better caching of item tile images.
authoroskar.wiksten <oskar.wiksten@08aca716-68be-ccc6-4d58-36f5abd142ac>
Mon, 14 Nov 2011 19:48:18 +0000 (19:48 +0000)
committeroskar.wiksten <oskar.wiksten@08aca716-68be-ccc6-4d58-36f5abd142ac>
Mon, 14 Nov 2011 19:48:18 +0000 (19:48 +0000)
Corrected bug that caused the player to move without user input after combat.
Added safeguards to model state when buying and selling items (Issue 273, thanks chitul!)

git-svn-id: https://andors-trail.googlecode.com/svn/trunk@203 08aca716-68be-ccc6-4d58-36f5abd142ac

AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/InputController.java
AndorsTrail/src/com/gpl/rpg/AndorsTrail/controller/ItemController.java
AndorsTrail/src/com/gpl/rpg/AndorsTrail/model/item/ItemContainer.java
AndorsTrail/src/com/gpl/rpg/AndorsTrail/resource/tiles/TileCache.java
AndorsTrail/src/com/gpl/rpg/AndorsTrail/util/LruCache.java [new file with mode: 0644]

index 3b7127405e89152025e18bac85595e1a95f50440..b128fd9633bd3a1aa4cabcace3470f2f677bbbe3 100644 (file)
@@ -67,6 +67,7 @@ public final class InputController implements OnClickListener, OnLongClickListen
        
        @Override
        public void onClick(View arg0) {
+               if (!model.uiSelections.isInCombat) return;
                onRelativeMovement(lastTouchPosition_dx, lastTouchPosition_dy);
     }
     
index c11a9f887de353e9c37fee852d617621c5555ed3..23d9b33217e5d86449dbe33b143b19f27b67f9f3 100644 (file)
@@ -214,17 +214,19 @@ public final class ItemController {
        }
        public static void sell(Player player, ItemType itemType, ItemContainer merchant, int quantity) {
                int price = getSellingPrice(player, itemType) * quantity;
-               player.inventory.gold += price;
-               player.inventory.removeItem(itemType.id, quantity);
-               merchant.addItem(itemType, quantity);
+               if (player.inventory.removeItem(itemType.id, quantity)) {
+                       player.inventory.gold += price;
+                       merchant.addItem(itemType, quantity);
+               }
        }
        public static void buy(ModelContainer model, Player player, ItemType itemType, ItemContainer merchant, int quantity) {
                int price = getBuyingPrice(player, itemType) * quantity;
                if (!canAfford(player, price)) return;
-               player.inventory.gold -= price;
-               player.inventory.addItem(itemType, quantity);
-               merchant.removeItem(itemType.id, quantity);
-               model.statistics.addGoldSpent(price);
+               if (merchant.removeItem(itemType.id, quantity)) {
+                       player.inventory.gold -= price;
+                       player.inventory.addItem(itemType, quantity);
+                       model.statistics.addGoldSpent(price);
+               }
        }
 
        public void quickitemUse(int quickSlotId) {
index 95f44a4d514769604a7ef17cc9eba84afe4b489d..e0a12111580c41096fa80f6ddf458f52dd6aa9bd 100644 (file)
@@ -71,10 +71,12 @@ public class ItemContainer {
                        }\r
                }\r
                if (index < 0) return false;\r
-               if (e.quantity <= quantity) {\r
+               if (e.quantity == quantity) {\r
                        items.remove(index);\r
-               } else {\r
+               } else if (e.quantity > quantity) {\r
                        e.quantity -= quantity;\r
+               } else {\r
+                       return false;\r
                }\r
                return true;\r
        }\r
index 7563c2c0725e95f1551814c80587b1ed4a25b6ad..09a8fd463f20dfe70834d3dc3bee320955ac216e 100644 (file)
@@ -2,13 +2,14 @@ package com.gpl.rpg.AndorsTrail.resource.tiles;
 
 import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
-import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map.Entry;
 
 import com.gpl.rpg.AndorsTrail.AndorsTrailApplication;
 import com.gpl.rpg.AndorsTrail.util.L;
+import com.gpl.rpg.AndorsTrail.util.LruCache;
 
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -17,7 +18,8 @@ public final class TileCache {
 
        private final ReferenceQueue<Bitmap> gcQueue = new ReferenceQueue<Bitmap>(); 
        private ResourceFileTile[] resourceTiles = new ResourceFileTile[1];
-       private HashMap<String, HashMap<Integer, Integer>> tileIDsPerTilesetAndLocalID = new HashMap<String, HashMap<Integer,Integer>>();
+       private final HashMap<String, HashMap<Integer, Integer>> tileIDsPerTilesetAndLocalID = new HashMap<String, HashMap<Integer,Integer>>();
+       private final LruCache<Integer, Bitmap> cache = new LruCache<Integer, Bitmap>(1000); 
        
        public int getMaxTileID() { return resourceTiles.length-1; }
        public void allocateMaxTileID(int maxTileID) {
@@ -43,7 +45,7 @@ public final class TileCache {
        private static final class ResourceFileTile {
                public final ResourceFileTileset tileset;
                public final int localID;
-               public SoftReference<Bitmap> bitmap;
+               //public WeakReference<Bitmap> bitmap;
                public ResourceFileTile(ResourceFileTileset tileset, int localID) {
                        this.tileset = tileset;
                        this.localID = localID;
@@ -84,8 +86,7 @@ public final class TileCache {
                                int tileID = j.getKey();
                                ResourceFileTile tile = j.getValue();
                                
-                               Bitmap bitmap = null;
-                               if (tile.bitmap != null) bitmap = tile.bitmap.get();
+                               Bitmap bitmap = cache.get(tileID);
                                
                                if (bitmap == null) {
                                        if (cutter == null) {
@@ -98,7 +99,8 @@ public final class TileCache {
                                        }
                                        
                                        bitmap = cutter.createTile(tile.localID);
-                                       tile.bitmap = new SoftReference<Bitmap>(bitmap, gcQueue);
+                                       cache.put(tileID, bitmap);
+                                       new WeakReference<Bitmap>(bitmap, gcQueue);
                                }
                                result.setBitmap(tileID, bitmap);
                        }
@@ -112,10 +114,8 @@ public final class TileCache {
        public Bitmap loadSingleTile(int tileID, Resources r) {
                cleanQueue();
                ResourceFileTile tile = resourceTiles[tileID];
-               if (tile.bitmap != null) {
-                       Bitmap bitmap = tile.bitmap.get();
-                       if (bitmap != null) return bitmap;
-               }
+               Bitmap bitmap = cache.get(tileID);
+               if (bitmap != null) return bitmap;
                
                if (AndorsTrailApplication.DEVELOPMENT_DEBUGMESSAGES) {
                        L.log("Loading single tile from tileset " + tile.tileset.tilesetName);
@@ -123,7 +123,7 @@ public final class TileCache {
                TileCutter cutter = new TileCutter(tile.tileset, r);
                Bitmap result = cutter.createTile(tile.localID);
                cutter.recycle();
-               tile.bitmap = new SoftReference<Bitmap>(result, gcQueue);
+               cache.put(tileID, result);
                return result;
        }
 }
diff --git a/AndorsTrail/src/com/gpl/rpg/AndorsTrail/util/LruCache.java b/AndorsTrail/src/com/gpl/rpg/AndorsTrail/util/LruCache.java
new file mode 100644 (file)
index 0000000..a744c6b
--- /dev/null
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.gpl.rpg.AndorsTrail.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Static library version of {@code android.util.LruCache}. Used to write apps
+ * that run on API levels prior to 12. When running on API level 12 or above,
+ * this implementation is still used; it does not try to switch to the
+ * framework's implementation. See the framework SDK documentation for a class
+ * overview.
+ */
+public class LruCache<K, V> {
+    private final LinkedHashMap<K, V> map;
+
+    /** Size of this cache in units. Not necessarily the number of elements. */
+    private int size;
+    private int maxSize;
+
+    private int putCount;
+    private int createCount;
+    private int evictionCount;
+    private int hitCount;
+    private int missCount;
+
+    /**
+     * @param maxSize for caches that do not override {@link #sizeOf}, this is
+     *     the maximum number of entries in the cache. For all other caches,
+     *     this is the maximum sum of the sizes of the entries in this cache.
+     */
+    public LruCache(int maxSize) {
+        if (maxSize <= 0) {
+            throw new IllegalArgumentException("maxSize <= 0");
+        }
+        this.maxSize = maxSize;
+        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
+    }
+
+    /**
+     * Returns the value for {@code key} if it exists in the cache or can be
+     * created by {@code #create}. If a value was returned, it is moved to the
+     * head of the queue. This returns null if a value is not cached and cannot
+     * be created.
+     */
+    public final V get(K key) {
+        if (key == null) {
+            throw new NullPointerException("key == null");
+        }
+
+        V mapValue;
+        synchronized (this) {
+            mapValue = map.get(key);
+            if (mapValue != null) {
+                hitCount++;
+                return mapValue;
+            }
+            missCount++;
+        }
+
+        /*
+         * Attempt to create a value. This may take a long time, and the map
+         * may be different when create() returns. If a conflicting value was
+         * added to the map while create() was working, we leave that value in
+         * the map and release the created value.
+         */
+
+        V createdValue = create(key);
+        if (createdValue == null) {
+            return null;
+        }
+
+        synchronized (this) {
+            createCount++;
+            mapValue = map.put(key, createdValue);
+
+            if (mapValue != null) {
+                // There was a conflict so undo that last put
+                map.put(key, mapValue);
+            } else {
+                size += safeSizeOf(key, createdValue);
+            }
+        }
+
+        if (mapValue != null) {
+            entryRemoved(false, key, createdValue, mapValue);
+            return mapValue;
+        } else {
+            trimToSize(maxSize);
+            return createdValue;
+        }
+    }
+
+    /**
+     * Caches {@code value} for {@code key}. The value is moved to the head of
+     * the queue.
+     *
+     * @return the previous value mapped by {@code key}.
+     */
+    public final V put(K key, V value) {
+        if (key == null || value == null) {
+            throw new NullPointerException("key == null || value == null");
+        }
+
+        V previous;
+        synchronized (this) {
+            putCount++;
+            size += safeSizeOf(key, value);
+            previous = map.put(key, value);
+            if (previous != null) {
+                size -= safeSizeOf(key, previous);
+            }
+        }
+
+        if (previous != null) {
+            entryRemoved(false, key, previous, value);
+        }
+
+        trimToSize(maxSize);
+        return previous;
+    }
+
+    /**
+     * @param maxSize the maximum size of the cache before returning. May be -1
+     *     to evict even 0-sized elements.
+     */
+    private void trimToSize(int maxSize) {
+        while (true) {
+            K key;
+            V value;
+            synchronized (this) {
+                if (size < 0 || (map.isEmpty() && size != 0)) {
+                    throw new IllegalStateException(getClass().getName()
+                            + ".sizeOf() is reporting inconsistent results!");
+                }
+
+                if (size <= maxSize || map.isEmpty()) {
+                    break;
+                }
+
+                Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
+                key = toEvict.getKey();
+                value = toEvict.getValue();
+                map.remove(key);
+                size -= safeSizeOf(key, value);
+                evictionCount++;
+            }
+
+            entryRemoved(true, key, value, null);
+        }
+    }
+
+    /**
+     * Removes the entry for {@code key} if it exists.
+     *
+     * @return the previous value mapped by {@code key}.
+     */
+    public final V remove(K key) {
+        if (key == null) {
+            throw new NullPointerException("key == null");
+        }
+
+        V previous;
+        synchronized (this) {
+            previous = map.remove(key);
+            if (previous != null) {
+                size -= safeSizeOf(key, previous);
+            }
+        }
+
+        if (previous != null) {
+            entryRemoved(false, key, previous, null);
+        }
+
+        return previous;
+    }
+
+    /**
+     * Called for entries that have been evicted or removed. This method is
+     * invoked when a value is evicted to make space, removed by a call to
+     * {@link #remove}, or replaced by a call to {@link #put}. The default
+     * implementation does nothing.
+     *
+     * <p>The method is called without synchronization: other threads may
+     * access the cache while this method is executing.
+     *
+     * @param evicted true if the entry is being removed to make space, false
+     *     if the removal was caused by a {@link #put} or {@link #remove}.
+     * @param newValue the new value for {@code key}, if it exists. If non-null,
+     *     this removal was caused by a {@link #put}. Otherwise it was caused by
+     *     an eviction or a {@link #remove}.
+     */
+    protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
+
+    /**
+     * Called after a cache miss to compute a value for the corresponding key.
+     * Returns the computed value or null if no value can be computed. The
+     * default implementation returns null.
+     *
+     * <p>The method is called without synchronization: other threads may
+     * access the cache while this method is executing.
+     *
+     * <p>If a value for {@code key} exists in the cache when this method
+     * returns, the created value will be released with {@link #entryRemoved}
+     * and discarded. This can occur when multiple threads request the same key
+     * at the same time (causing multiple values to be created), or when one
+     * thread calls {@link #put} while another is creating a value for the same
+     * key.
+     */
+    protected V create(K key) {
+        return null;
+    }
+
+    private int safeSizeOf(K key, V value) {
+        int result = sizeOf(key, value);
+        if (result < 0) {
+            throw new IllegalStateException("Negative size: " + key + "=" + value);
+        }
+        return result;
+    }
+
+    /**
+     * Returns the size of the entry for {@code key} and {@code value} in
+     * user-defined units.  The default implementation returns 1 so that size
+     * is the number of entries and max size is the maximum number of entries.
+     *
+     * <p>An entry's size must not change while it is in the cache.
+     */
+    protected int sizeOf(K key, V value) {
+        return 1;
+    }
+
+    /**
+     * Clear the cache, calling {@link #entryRemoved} on each removed entry.
+     */
+    public final void evictAll() {
+        trimToSize(-1); // -1 will evict 0-sized elements
+    }
+
+    /**
+     * For caches that do not override {@link #sizeOf}, this returns the number
+     * of entries in the cache. For all other caches, this returns the sum of
+     * the sizes of the entries in this cache.
+     */
+    public synchronized final int size() {
+        return size;
+    }
+
+    /**
+     * For caches that do not override {@link #sizeOf}, this returns the maximum
+     * number of entries in the cache. For all other caches, this returns the
+     * maximum sum of the sizes of the entries in this cache.
+     */
+    public synchronized final int maxSize() {
+        return maxSize;
+    }
+
+    /**
+     * Returns the number of times {@link #get} returned a value.
+     */
+    public synchronized final int hitCount() {
+        return hitCount;
+    }
+
+    /**
+     * Returns the number of times {@link #get} returned null or required a new
+     * value to be created.
+     */
+    public synchronized final int missCount() {
+        return missCount;
+    }
+
+    /**
+     * Returns the number of times {@link #create(Object)} returned a value.
+     */
+    public synchronized final int createCount() {
+        return createCount;
+    }
+
+    /**
+     * Returns the number of times {@link #put} was called.
+     */
+    public synchronized final int putCount() {
+        return putCount;
+    }
+
+    /**
+     * Returns the number of values that have been evicted.
+     */
+    public synchronized final int evictionCount() {
+        return evictionCount;
+    }
+
+    /**
+     * Returns a copy of the current contents of the cache, ordered from least
+     * recently accessed to most recently accessed.
+     */
+    public synchronized final Map<K, V> snapshot() {
+        return new LinkedHashMap<K, V>(map);
+    }
+
+    @Override public synchronized final String toString() {
+        int accesses = hitCount + missCount;
+        int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0;
+        return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]",
+                maxSize, hitCount, missCount, hitPercent);
+    }
+}