/* * Copyright 2012, 2013 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * This program 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 Lesser General License for more details. * * You should have received a copy of the GNU Lesser General License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.oscim.layers.tile; import static org.oscim.layers.tile.MapTile.State.CANCEL; import static org.oscim.layers.tile.MapTile.State.DEADBEEF; import static org.oscim.layers.tile.MapTile.State.LOADING; import static org.oscim.layers.tile.MapTile.State.NEW_DATA; import static org.oscim.layers.tile.MapTile.State.NONE; import static org.oscim.layers.tile.MapTile.State.READY; import org.oscim.core.Tile; import org.oscim.layers.tile.vector.VectorTileLoader; import org.oscim.layers.tile.vector.labeling.LabelTileLoaderHook; import org.oscim.renderer.bucket.RenderBuckets; import org.oscim.utils.pool.Inlist; import org.oscim.utils.quadtree.TileIndex; import org.oscim.utils.quadtree.TreeNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Extends Tile class to hold state and data. * * Used concurrently in: TileManager (Main Thread), TileLoader (Worker Thread) * and TileRenderer (GL Thread). */ public class MapTile extends Tile { static final Logger log = LoggerFactory.getLogger(MapTile.class); public static class TileNode extends TreeNode<TileNode, MapTile> { } public static final class State { public final static byte NONE = (1 << 0); /** * STATE_LOADING means the tile is about to be loaded / loading. * Tile belongs to TileLoader thread. */ public final static byte LOADING = (1 << 1); /** * STATE_NEW_DATA: tile data is prepared for rendering. * While 'locked' it belongs to GL Thread. */ public final static byte NEW_DATA = (1 << 2); /** * STATE_READY: tile data is uploaded to GL. * While 'locked' it belongs to GL Thread. */ public final static byte READY = (1 << 3); /** * STATE_CANCEL: tile is removed from TileManager, * but may still be processed by TileLoader. */ public final static byte CANCEL = (1 << 4); /** * Dont touch if you find some. */ public final static byte DEADBEEF = (1 << 6); } public final static int PROXY_CHILD00 = (1 << 0); public final static int PROXY_CHILD01 = (1 << 1); public final static int PROXY_CHILD10 = (1 << 2); public final static int PROXY_CHILD11 = (1 << 3); public final static int PROXY_PARENT = (1 << 4); public final static int PROXY_GRAMPA = (1 << 5); public final static int PROXY_HOLDER = (1 << 6); /** Tile state */ byte state = State.NONE; /** * absolute tile coordinates: tileX,Y / Math.pow(2, zoomLevel) */ public final double x; public final double y; /** * List of TileData for rendering. ElementLayers is always at first * position (for VectorTileLayer). TileLoaderHooks may add additional * data. See e.g. {@link LabelTileLoaderHook}. */ public TileData data; /** * Pointer to access relatives in {@link TileIndex} */ public final TileNode node; /** * current distance from map center */ public float distance; /** * Tile is in view region. Set by TileRenderer. */ public boolean isVisible; /** * Used for fade-effects */ public long fadeTime; /** * Used to avoid drawing a tile twice per frame */ int lastDraw = 0; /** Keep track which tiles are locked as proxy for this tile */ private int proxy = 0; /** Tile lock counter, synced in TileManager */ private int locked = 0; private int refs = 0; /** * Only used GLRenderer when this tile sits in for another tile. * e.g. x:-1,y:0,z:1 for x:1,y:0 */ MapTile holder; public static abstract class TileData extends Inlist<TileData> { Object id; protected abstract void dispose(); public TileData next() { return (TileData) next; } } public MapTile(TileNode node, int tileX, int tileY, int zoomLevel) { super(tileX, tileY, (byte) zoomLevel); this.x = (double) tileX / (1 << zoomLevel); this.y = (double) tileY / (1 << zoomLevel); this.node = node; } public boolean state(int testState) { return (state & testState) != 0; } public int getState() { return state; } /** * @return true when tile could be used by another thread. */ boolean isLocked() { return locked > 0 || refs > 0; } /** * Set this tile to be locked, i.e. to no be modified or cleared * while rendering. Renderable parent, grand-parent and children * will also be locked. Dont forget to unlock when tile is not longer * used. This function should only be called through {@link TileManager} */ void lock() { if (state == DEADBEEF) { log.debug("Locking dead tile {}", this); return; } if (locked++ > 0) return; MapTile p; /* lock all tiles that could serve as proxy */ for (int i = 0; i < 4; i++) { p = node.child(i); if (p == null) continue; if (p.state(READY | NEW_DATA)) { proxy |= (1 << i); p.refs++; } } if (node.isRoot()) return; p = node.parent(); if (p != null && p.state(READY | NEW_DATA)) { proxy |= PROXY_PARENT; p.refs++; } if (node.parent.isRoot()) return; p = node.parent.parent(); if (p != null && p.state(READY | NEW_DATA)) { proxy |= PROXY_GRAMPA; p.refs++; } } /** * Unlocks this tile when it cannot be used by render-thread. */ void unlock() { if (--locked > 0) return; TileNode parent = node.parent; if ((proxy & PROXY_PARENT) != 0) parent.item.refs--; if ((proxy & PROXY_GRAMPA) != 0) { parent = parent.parent; parent.item.refs--; } for (int i = 0; i < 4; i++) { if ((proxy & (1 << i)) != 0) node.child(i).refs--; } /* removed all proxy references for this tile */ proxy = 0; if (state == DEADBEEF) { log.debug("Unlock dead tile {}", this); clear(); } } /** * @return true if tile is state is not NONE. */ public boolean isActive() { return state > State.NONE; } /** * Test whether it is save to access a proxy item * through this.node.* */ public boolean hasProxy(int proxy) { return (this.proxy & proxy) != 0; } /** * CAUTION: This function may only be called * by {@link TileManager} */ protected void clear() { while (data != null) { data.dispose(); data = data.next; } setState(NONE); } /** * Get the default ElementLayers which are added * by {@link VectorTileLoader} */ public RenderBuckets getBuckets() { if (!(data instanceof RenderBuckets)) return null; return (RenderBuckets) data; } public TileData getData(Object id) { for (TileData d = data; d != null; d = d.next) if (d.id == id) return d; return null; } public void addData(Object id, TileData td) { /* keeping ElementLayers at position 0! */ td.id = id; if (data != null) { td.next = data.next; data.next = td; } else { data = td; } } public TileData removeData(Object id) { if (data == null) return null; TileData prev = data; if (data.id == id) { data = data.next; return prev; } for (TileData d = data.next; d != null; d = d.next) { if (d.id == id) { prev.next = d.next; return d; } prev = d; } return null; } public static int depthOffset(MapTile t) { return ((t.tileX % 4) + (t.tileY % 4 * 4) + 1); } public MapTile getProxyChild(int id, byte state) { if ((proxy & (1 << id)) == 0) return null; MapTile child = node.child(id); if (child == null || (child.state & state) == 0) return null; return child; } public MapTile getParent() { if ((proxy & PROXY_PARENT) == 0) return null; return node.parent.item; } public MapTile getProxy(int proxy, byte state) { if ((this.proxy & proxy) == 0) return null; MapTile p = null; switch (proxy) { case PROXY_CHILD00: p = node.child(0); break; case PROXY_CHILD01: p = node.child(1); break; case PROXY_CHILD10: p = node.child(2); break; case PROXY_CHILD11: p = node.child(3); break; case PROXY_PARENT: p = node.parent(); break; case PROXY_GRAMPA: p = node.parent.parent(); break; case PROXY_HOLDER: p = holder; break; } if (p == null || (p.state & state) == 0) return null; return p; } public String state() { switch (state) { case State.NONE: return "None"; case State.LOADING: return "Loading"; case State.NEW_DATA: return "Data"; case State.READY: return "Ready"; case State.CANCEL: return "Cancel"; case State.DEADBEEF: return "Dead"; } return ""; } public synchronized void setState(byte newState) { if (state == newState) return; /* Renderer could have uploaded the tile while the layer * was cleared. This prevents to set tile to READY state. */ /* All other state changes are on the main-thread. */ if (state == DEADBEEF) return; switch (newState) { case NONE: state = newState; return; case LOADING: if (state == NONE) { state = newState; return; } throw new IllegalStateException("Loading" + " <= " + state() + " " + this); case NEW_DATA: if (state == LOADING) { state = newState; return; } throw new IllegalStateException("NewData" + " <= " + state() + " " + this); case READY: if (state == NEW_DATA) { state = newState; return; } throw new IllegalStateException("Ready" + " <= " + state() + " " + this); case CANCEL: if (state == LOADING) { state = newState; return; } throw new IllegalStateException("Cancel" + " <= " + state() + " " + this); case DEADBEEF: state = newState; return; } } }