- use only one RenderInstruction cache item when instructions are the same for multiple zoom-level

- do common checks in Rule instead of calling interface methods
This commit is contained in:
Hannes Janetzek 2013-01-24 22:04:39 +01:00
parent b50481653a
commit bf75d3bead
6 changed files with 211 additions and 108 deletions

View File

@ -19,26 +19,22 @@ import org.oscim.core.Tag;
class MatchingCacheKey { class MatchingCacheKey {
int mHashCodeValue; int mHashCodeValue;
Tag[] mTags; Tag[] mTags;
byte mZoomLevel;
MatchingCacheKey() { MatchingCacheKey() {
} }
MatchingCacheKey(Tag[] tags, byte zoomLevel) { MatchingCacheKey(Tag[] tags) {
mTags = tags; mTags = tags;
mZoomLevel = zoomLevel;
mHashCodeValue = calculateHashCode(); mHashCodeValue = calculateHashCode();
} }
MatchingCacheKey(MatchingCacheKey key) { MatchingCacheKey(MatchingCacheKey key) {
mTags = key.mTags; mTags = key.mTags;
mZoomLevel = key.mZoomLevel;
mHashCodeValue = key.mHashCodeValue; mHashCodeValue = key.mHashCodeValue;
} }
void set(Tag[] tags, byte zoomLevel) { void set(Tag[] tags) {
mTags = tags; mTags = tags;
mZoomLevel = zoomLevel;
int result = 7; int result = 7;
@ -47,7 +43,7 @@ class MatchingCacheKey {
break; break;
result = 31 * result + mTags[i].hashCode(); result = 31 * result + mTags[i].hashCode();
} }
result = 31 * result + mZoomLevel; result = 31 * result;
mHashCodeValue = result; mHashCodeValue = result;
} }
@ -57,15 +53,9 @@ class MatchingCacheKey {
if (this == obj) { if (this == obj) {
return true; return true;
} }
// else if (!(obj instanceof MatchingCacheKey)) {
// return false;
// }
MatchingCacheKey other = (MatchingCacheKey) obj; MatchingCacheKey other = (MatchingCacheKey) obj;
if (mZoomLevel != other.mZoomLevel)
return false;
if (mTags == null) { if (mTags == null) {
return (other.mTags == null); return (other.mTags == null);
} else if (other.mTags == null) } else if (other.mTags == null)
@ -76,10 +66,14 @@ class MatchingCacheKey {
return false; return false;
} }
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++) {
if (mTags[i] != other.mTags[i]) if (mTags[i] == other.mTags[i])
return false; continue;
if (mTags[i].key == other.mTags[i].key && mTags[i].value == other.mTags[i].value)
continue;
return false;
}
return true; return true;
} }
@ -99,7 +93,7 @@ class MatchingCacheKey {
break; break;
result = 31 * result + mTags[i].hashCode(); result = 31 * result + mTags[i].hashCode();
} }
result = 31 * result + mZoomLevel; result = 31 * result;
return result; return result;
} }

View File

@ -27,17 +27,12 @@ class NegativeRule extends Rule {
} }
@Override @Override
boolean matchesNode(Tag[] tags, byte zoomLevel) { boolean matchesNode(Tag[] tags) {
return mZoomMin <= zoomLevel && mZoomMax >= zoomLevel return mAttributeMatcher.matches(tags);
&& (mElement != Element.WAY)
&& mAttributeMatcher.matches(tags);
} }
@Override @Override
boolean matchesWay(Tag[] tags, byte zoomLevel, int closed) { boolean matchesWay(Tag[] tags) {
return mZoomMin <= zoomLevel && mZoomMax >= zoomLevel return mAttributeMatcher.matches(tags);
&& (mElement != Element.NODE)
&& (mClosed == closed || mClosed == Closed.ANY)
&& mAttributeMatcher.matches(tags);
} }
} }

View File

@ -36,21 +36,14 @@ class PositiveRule extends Rule {
} }
@Override @Override
boolean matchesNode(Tag[] tags, byte zoomLevel) { boolean matchesNode(Tag[] tags) {
return (mElement != Element.WAY) return (mKeyMatcher == null || mKeyMatcher.matches(tags))
&& mZoomMin <= zoomLevel
&& mZoomMax >= zoomLevel
&& (mKeyMatcher == null || mKeyMatcher.matches(tags))
&& (mValueMatcher == null || mValueMatcher.matches(tags)); && (mValueMatcher == null || mValueMatcher.matches(tags));
} }
@Override @Override
boolean matchesWay(Tag[] tags, byte zoomLevel, int closed) { boolean matchesWay(Tag[] tags) {
return (mElement != Element.NODE) return (mKeyMatcher == null || mKeyMatcher.matches(tags)) &&
&& mZoomMin <= zoomLevel (mValueMatcher == null || mValueMatcher.matches(tags));
&& mZoomMax >= zoomLevel
&& (mClosed == closed || mClosed == Closed.ANY)
&& (mKeyMatcher == null || mKeyMatcher.matches(tags))
&& (mValueMatcher == null || mValueMatcher.matches(tags));
} }
} }

View File

@ -28,7 +28,9 @@ import android.graphics.Color;
* A RenderTheme defines how ways and nodes are drawn. * A RenderTheme defines how ways and nodes are drawn.
*/ */
public class RenderTheme { public class RenderTheme {
private static final int MATCHING_CACHE_SIZE = 1024; private final static String TAG = RenderTheme.class.getName();
private static final int MATCHING_CACHE_SIZE = 512;
private static final int RENDER_THEME_VERSION = 1; private static final int RENDER_THEME_VERSION = 1;
private static void validate(String elementName, Integer version, private static void validate(String elementName, Integer version,
@ -82,9 +84,15 @@ public class RenderTheme {
private final int mMapBackground; private final int mMapBackground;
private final ArrayList<Rule> mRulesList; private final ArrayList<Rule> mRulesList;
private final LRUCache<MatchingCacheKey, RenderInstruction[]> mMatchingCacheNodes; private final LRUCache<MatchingCacheKey, RenderInstructionItem> mMatchingCacheNodes;
private final LRUCache<MatchingCacheKey, RenderInstruction[]> mMatchingCacheWay; private final LRUCache<MatchingCacheKey, RenderInstructionItem> mMatchingCacheWay;
private final LRUCache<MatchingCacheKey, RenderInstruction[]> mMatchingCacheArea; private final LRUCache<MatchingCacheKey, RenderInstructionItem> mMatchingCacheArea;
class RenderInstructionItem {
RenderInstructionItem next;
int zoom;
RenderInstruction[] list;
}
RenderTheme(int mapBackground, float baseStrokeWidth, float baseTextSize) { RenderTheme(int mapBackground, float baseStrokeWidth, float baseTextSize) {
mMapBackground = mapBackground; mMapBackground = mapBackground;
@ -92,11 +100,11 @@ public class RenderTheme {
mBaseTextSize = baseTextSize; mBaseTextSize = baseTextSize;
mRulesList = new ArrayList<Rule>(); mRulesList = new ArrayList<Rule>();
mMatchingCacheNodes = new LRUCache<MatchingCacheKey, RenderInstruction[]>( mMatchingCacheNodes = new LRUCache<MatchingCacheKey, RenderInstructionItem>(
MATCHING_CACHE_SIZE); MATCHING_CACHE_SIZE);
mMatchingCacheWay = new LRUCache<MatchingCacheKey, RenderInstruction[]>( mMatchingCacheWay = new LRUCache<MatchingCacheKey, RenderInstructionItem>(
MATCHING_CACHE_SIZE); MATCHING_CACHE_SIZE);
mMatchingCacheArea = new LRUCache<MatchingCacheKey, RenderInstruction[]>( mMatchingCacheArea = new LRUCache<MatchingCacheKey, RenderInstructionItem>(
MATCHING_CACHE_SIZE); MATCHING_CACHE_SIZE);
} }
@ -130,6 +138,12 @@ public class RenderTheme {
return mMapBackground; return mMapBackground;
} }
private static void render(IRenderCallback renderCallback,
RenderInstruction[] renderInstructions, Tag[] tags) {
for (int i = 0, n = renderInstructions.length; i < n; i++)
renderInstructions[i].renderNode(renderCallback, tags);
}
/** /**
* @param renderCallback * @param renderCallback
* ... * ...
@ -142,41 +156,92 @@ public class RenderTheme {
public synchronized RenderInstruction[] matchNode(IRenderCallback renderCallback, public synchronized RenderInstruction[] matchNode(IRenderCallback renderCallback,
Tag[] tags, byte zoomLevel) { Tag[] tags, byte zoomLevel) {
RenderInstruction[] renderInstructions = null; // list of renderinsctruction items in cache
RenderInstructionItem ris = null;
MatchingCacheKey matchingCacheKey; // the item matching tags and zoomlevel
RenderInstructionItem ri = null;
matchingCacheKey = new MatchingCacheKey(tags, zoomLevel); MatchingCacheKey matchingCacheKey = new MatchingCacheKey(tags);
boolean found = mMatchingCacheNodes.containsKey(matchingCacheKey); boolean found = mMatchingCacheNodes.containsKey(matchingCacheKey);
int zoomMask = 1 << zoomLevel;
if (found) { if (found) {
renderInstructions = mMatchingCacheNodes.get(matchingCacheKey); ris = mMatchingCacheNodes.get(matchingCacheKey);
} else {
for (ri = ris; ri != null; ri = ri.next)
if ((ri.zoom & zoomMask) != 0)
// cache hit
break;
}
if (ri == null) {
// cache miss // cache miss
List<RenderInstruction> matchingList = new ArrayList<RenderInstruction>(4); List<RenderInstruction> matches = mMatchingList;
matches.clear();
for (int i = 0, n = mRulesList.size(); i < n; ++i) for (int i = 0, n = mRulesList.size(); i < n; ++i)
mRulesList.get(i) mRulesList.get(i).matchNode(renderCallback, tags, zoomLevel, matches);
.matchNode(renderCallback, tags, zoomLevel, matchingList);
int size = matches.size();
// check if same instructions are used in another level
for (ri = ris; ri != null; ri = ri.next) {
if (size == 0) {
if (ri.list != null)
continue;
// both matchinglists are empty
break;
}
if (ri.list == null)
continue;
if (ri.list.length != size)
continue;
int i = 0;
for (RenderInstruction r : ri.list) {
if (r != matches.get(i))
break;
i++;
}
if (i == size)
// both matching lists contain the same items
break;
}
if (ri != null) {
// we found a same matchting list on another zoomlevel
ri.zoom |= zoomMask;
} else {
ri = new RenderInstructionItem();
ri.zoom = zoomMask;
int size = matchingList.size();
if (size > 0) { if (size > 0) {
renderInstructions = new RenderInstruction[size]; ri.list = new RenderInstruction[size];
matchingList.toArray(renderInstructions); matches.toArray(ri.list);
}
// attach this list to the one found for MatchingKey
if (ris != null)
ris.next = ri;
else
mMatchingCacheNodes.put(matchingCacheKey, ri);
} }
mMatchingCacheNodes.put(matchingCacheKey, renderInstructions);
} }
if (renderInstructions != null) { if (ri.list != null)
for (int i = 0, n = renderInstructions.length; i < n; i++) render(renderCallback, ri.list, tags);
renderInstructions[i].renderNode(renderCallback, tags);
}
return renderInstructions; return ri.list;
} }
// private int missCnt = 0; private int missCnt = 0;
// private int hitCnt = 0; private int hitCnt = 0;
private MatchingCacheKey mCacheKey = new MatchingCacheKey(); private MatchingCacheKey mCacheKey = new MatchingCacheKey();
private ArrayList<RenderInstruction> mMatchingList = new ArrayList<RenderInstruction>(4);
/** /**
* Matches a way with the given parameters against this RenderTheme. * Matches a way with the given parameters against this RenderTheme.
@ -196,9 +261,13 @@ public class RenderTheme {
public synchronized RenderInstruction[] matchWay(IRenderCallback renderCallback, public synchronized RenderInstruction[] matchWay(IRenderCallback renderCallback,
Tag[] tags, byte zoomLevel, boolean closed, boolean render) { Tag[] tags, byte zoomLevel, boolean closed, boolean render) {
RenderInstruction[] renderInstructions = null; // list of renderinsctruction items in cache
RenderInstructionItem ris = null;
LRUCache<MatchingCacheKey, RenderInstruction[]> matchingCache; // the item matching tags and zoomlevel
RenderInstructionItem ri = null;
LRUCache<MatchingCacheKey, RenderInstructionItem> matchingCache;
MatchingCacheKey matchingCacheKey; MatchingCacheKey matchingCacheKey;
if (closed) { if (closed) {
@ -206,38 +275,87 @@ public class RenderTheme {
} else { } else {
matchingCache = mMatchingCacheWay; matchingCache = mMatchingCacheWay;
} }
int zoomMask = 1 << zoomLevel;
mCacheKey.set(tags, zoomLevel); mCacheKey.set(tags);
renderInstructions = matchingCache.get(mCacheKey); ris = matchingCache.get(mCacheKey);
if (renderInstructions != null) { for (ri = ris; ri != null; ri = ri.next)
// Log.d("RenderTheme", hitCnt++ + "Cache Hit"); if ((ri.zoom & zoomMask) != 0)
} else if (!matchingCache.containsKey(mCacheKey)) { // cache hit
matchingCacheKey = new MatchingCacheKey(mCacheKey); break;
if (ri == null) {
// cache miss // cache miss
// Log.d("RenderTheme", missCnt++ + " Cache Miss"); //Log.d(TAG, missCnt++ + " / " + hitCnt + " Cache Miss");
int c = (closed ? Closed.YES : Closed.NO); int c = (closed ? Closed.YES : Closed.NO);
List<RenderInstruction> matchingList = new ArrayList<RenderInstruction>(4); List<RenderInstruction> matches = mMatchingList;
for (int i = 0, n = mRulesList.size(); i < n; ++i) { matches.clear();
mRulesList.get(i).matchWay(renderCallback, tags, zoomLevel, c,
matchingList); for (int i = 0, n = mRulesList.size(); i < n; ++i)
mRulesList.get(i).matchWay(renderCallback, tags, zoomLevel, c, matches);
int size = matches.size();
// check if same instructions are used in another level
for (ri = ris; ri != null; ri = ri.next) {
if (size == 0) {
if (ri.list != null)
continue;
// both matchinglists are empty
break;
} }
int size = matchingList.size();
if (ri.list == null)
continue;
if (ri.list.length != size)
continue;
int i = 0;
for (RenderInstruction r : ri.list) {
if (r != matches.get(i))
break;
i++;
}
if (i == size)
// both matching lists contain the same items
break;
}
if (ri != null) {
// we found a same matchting list on another zoomlevel
ri.zoom |= zoomMask;
//Log.d(TAG, " same instructions " + size + " " + Arrays.deepToString(tags));
} else {
//Log.d(TAG, " new instructions " + size + " " + Arrays.deepToString(tags));
ri = new RenderInstructionItem();
ri.zoom = zoomMask;
if (size > 0) { if (size > 0) {
renderInstructions = new RenderInstruction[size]; ri.list = new RenderInstruction[size];
matchingList.toArray(renderInstructions); matches.toArray(ri.list);
}
matchingCache.put(matchingCacheKey, renderInstructions);
} }
if (render && renderInstructions != null) { // attach this list to the one found for MatchingKey
for (int i = 0, n = renderInstructions.length; i < n; i++) if (ris != null) {
renderInstructions[i].renderWay(renderCallback, tags); ris.next = ri;
}
else {
matchingCacheKey = new MatchingCacheKey(mCacheKey);
matchingCache.put(matchingCacheKey, ri);
}
} }
return renderInstructions; }
if (render && ri.list != null) {
for (int i = 0, n = ri.list.length; i < n; i++)
ri.list[i].renderWay(renderCallback, tags);
}
return ri.list;
} }
void addRule(Rule rule) { void addRule(Rule rule) {

View File

@ -173,8 +173,7 @@ abstract class Rule {
final int mElement; final int mElement;
final int mClosed; final int mClosed;
Rule(int element, int closed, byte zoomMin, Rule(int element, int closed, byte zoomMin, byte zoomMax) {
byte zoomMax) {
mClosed = closed; mClosed = closed;
mElement = element; mElement = element;
@ -193,13 +192,17 @@ abstract class Rule {
mSubRules.add(rule); mSubRules.add(rule);
} }
abstract boolean matchesNode(Tag[] tags, byte zoomLevel); abstract boolean matchesNode(Tag[] tags);
abstract boolean matchesWay(Tag[] tags, byte zoomLevel, int closed); abstract boolean matchesWay(Tag[] tags);
void matchNode(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel, void matchNode(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel,
List<RenderInstruction> matchingList) { List<RenderInstruction> matchingList) {
if (matchesNode(tags, zoomLevel)) { if ((mElement != Element.WAY)
&& mZoomMin <= zoomLevel
&& mZoomMax >= zoomLevel
&& matchesNode(tags)) {
for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) for (int i = 0, n = mRenderInstructionArray.length; i < n; i++)
matchingList.add(mRenderInstructionArray[i]); matchingList.add(mRenderInstructionArray[i]);
@ -210,13 +213,19 @@ abstract class Rule {
} }
void matchWay(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel, void matchWay(IRenderCallback renderCallback, Tag[] tags, byte zoomLevel,
int closed, int closed, List<RenderInstruction> matchingList) {
List<RenderInstruction> matchingList) {
if (matchesWay(tags, zoomLevel, closed)) { if ((mElement != Element.NODE)
&& mZoomMin <= zoomLevel
&& mZoomMax >= zoomLevel
&& (mClosed == closed || mClosed == Closed.ANY)
&& (matchesWay(tags))) {
// add instructions for this rule
for (int i = 0, n = mRenderInstructionArray.length; i < n; i++) for (int i = 0, n = mRenderInstructionArray.length; i < n; i++)
matchingList.add(mRenderInstructionArray[i]); matchingList.add(mRenderInstructionArray[i]);
// check subrules
for (int i = 0, n = mSubRuleArray.length; i < n; i++) for (int i = 0, n = mSubRuleArray.length; i < n; i++)
mSubRuleArray[i].matchWay(renderCallback, tags, zoomLevel, closed, mSubRuleArray[i].matchWay(renderCallback, tags, zoomLevel, closed,
matchingList); matchingList);
@ -230,15 +239,10 @@ abstract class Rule {
mRenderInstructionArray = new RenderInstruction[mRenderInstructions.size()]; mRenderInstructionArray = new RenderInstruction[mRenderInstructions.size()];
mRenderInstructions.toArray(mRenderInstructionArray); mRenderInstructions.toArray(mRenderInstructionArray);
// for (int i = 0, n = mRenderInstructions.size(); i < n; i++)
// mRenderInstructionArray[i] = mRenderInstructions.get(i);
mSubRuleArray = new Rule[mSubRules.size()]; mSubRuleArray = new Rule[mSubRules.size()];
mSubRules.toArray(mSubRuleArray); mSubRules.toArray(mSubRuleArray);
// for (int i = 0, n = mSubRules.size(); i < n; i++)
// mSubRuleArray[i] = mSubRules.get(i);
mRenderInstructions.clear(); mRenderInstructions.clear();
mRenderInstructions = null; mRenderInstructions = null;
mSubRules.clear(); mSubRules.clear();

View File

@ -17,7 +17,6 @@ package org.oscim.theme;
import java.util.Stack; import java.util.Stack;
final class RuleOptimizer { final class RuleOptimizer {
// private static final Logger LOG = Logger.getLogger(RuleOptimizer.class.getName());
private static AttributeMatcher optimizeKeyMatcher(AttributeMatcher attributeMatcher, private static AttributeMatcher optimizeKeyMatcher(AttributeMatcher attributeMatcher,
Stack<Rule> ruleStack) { Stack<Rule> ruleStack) {
@ -26,7 +25,7 @@ final class RuleOptimizer {
PositiveRule positiveRule = (PositiveRule) ruleStack.get(i); PositiveRule positiveRule = (PositiveRule) ruleStack.get(i);
if (positiveRule.mKeyMatcher != null if (positiveRule.mKeyMatcher != null
&& positiveRule.mKeyMatcher.isCoveredBy(attributeMatcher)) { && positiveRule.mKeyMatcher.isCoveredBy(attributeMatcher)) {
return null; // AnyMatcher.getInstance(); return null;
} }
} }
} }
@ -42,7 +41,7 @@ final class RuleOptimizer {
if (positiveRule.mValueMatcher != null if (positiveRule.mValueMatcher != null
&& positiveRule.mValueMatcher.isCoveredBy(attributeMatcher)) { && positiveRule.mValueMatcher.isCoveredBy(attributeMatcher)) {
return null; // AnyMatcher.getInstance(); return null;
} }
} }
} }