001/*
002 * Trident - A Multithreaded Server Alternative
003 * Copyright 2014 The TridentSDK Team
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *    http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package net.tridentsdk.server.chunk;
018
019import com.google.common.collect.Lists;
020import com.google.common.collect.Maps;
021import net.tridentsdk.server.concurrent.ThreadsHandler;
022import net.tridentsdk.server.world.TridentChunk;
023import net.tridentsdk.server.world.TridentWorld;
024import net.tridentsdk.world.ChunkLocation;
025
026import javax.annotation.concurrent.GuardedBy;
027import javax.annotation.concurrent.ThreadSafe;
028import java.util.Collection;
029import java.util.HashMap;
030import java.util.Set;
031import java.util.function.Consumer;
032
033/**
034 * Manages the chunks stored in memory per world
035 *
036 * @author The TridentSDK Team
037 */
038@ThreadSafe
039public class ChunkHandler {
040    @GuardedBy("counters")
041    private final HashMap<ChunkLocation, CRefCounter> counters = Maps.newHashMap();
042    private final TridentWorld world;
043
044    /**
045     * Creates a new chunk handler to manage the chunks of the provided world
046     *
047     * @param world the world to manage chunks for
048     */
049    public ChunkHandler(TridentWorld world) {
050        this.world = world;
051    }
052
053    /**
054     * Places a chunk into the collection of in-memory chunks
055     *
056     * @param chunk the chunk to add
057     */
058    public void put(TridentChunk chunk) {
059        synchronized (counters) {
060            counters.put(chunk.location(), CRefCounter.wrap(chunk));
061        }
062    }
063
064    /**
065     * Obtains the chunk at the given location in the world, generating if given to do so
066     *
067     * @param location the location to obtain the chunk
068     * @param gen      {@code true} to generate a new chunk if no chunk exists
069     * @return the chunk at the given location, or {@code null} if it doesn't exist and {@code gen} is false
070     */
071    public TridentChunk get(ChunkLocation location, boolean gen) {
072        if (gen) {
073            synchronized (counters) {
074                CRefCounter counter = counters.get(location);
075                if (counter == null) {
076                    return world.generateChunk(location);
077                } else return counter.unwrap();
078            }
079        } else {
080            CRefCounter refCounter = get(location);
081            return refCounter == null ? null : refCounter.unwrap();
082        }
083    }
084
085    /**
086     * Obtains the chunk reference counter at the specified location
087     *
088     * @param location the location to obtain the counter
089     * @return the counter at the location, or {@code null} if it doesn't exist
090     */
091    public CRefCounter get(ChunkLocation location) {
092        synchronized (counters) {
093            return counters.get(location);
094        }
095    }
096
097    /**
098     * Obtains the chunk reference counter and applies a transformation function
099     *
100     * @param location the location or obtain the chunk reference counter
101     * @param consumer the transformation function
102     * @return {@code true} to indicate that the chunk was successfully retrieved and transformed
103     */
104    public boolean apply(ChunkLocation location, Consumer<CRefCounter> consumer) {
105        CRefCounter chunk = get(location);
106        if (chunk != null) {
107            consumer.accept(chunk);
108            return true;
109        }
110
111        return false;
112    }
113
114    /**
115     * Attempts to remove the chunk from memory and save it
116     *
117     * <p>This method returns {@code false} if:
118     * <ul>
119     *     <li>The chunk is not loaded</li>
120     *     <li>The chunk still has strong references</li>
121     * </ul></p>
122     *
123     * @param location the location to remove the chunk
124     * @return {@code true} to signify that the collection was modified as a result of this operation
125     */
126    public boolean tryRemove(ChunkLocation location) {
127        synchronized (counters) {
128            CRefCounter chunk = get(location);
129            if (chunk == null) {
130                return false;
131            }
132
133            if (!chunk.hasStrongRefs()) {
134                TridentChunk c = chunk.unwrap();
135                if (chunk.hasWeakRefs()) {
136                    // TODO remove weak referencing items
137                }
138
139                ThreadsHandler.chunkExecutor().execute(c::unload);
140                remove(location);
141                return true;
142            }
143        }
144
145        return false;
146    }
147
148    /**
149     * Releases the reference counters associated with the chunks that are specified in the set given
150     *
151     * @param chunkSet the set of chunks to release references to, given that they exist in this cache
152     */
153    public void releaseReferences(ChunkLocationSet chunkSet) {
154        synchronized (counters) {
155            for (ChunkLocation location : chunkSet.locations()) {
156                CRefCounter counter = get(location);
157                if (counter != null) {
158                    counter.releaseStrong();
159                }
160            }
161        }
162    }
163
164    /**
165     * Manually removes the chunk from the collection without running any cleanup code
166     *
167     * @param location the location to remove the chunk from
168     */
169    public void remove(ChunkLocation location) {
170        synchronized (counters) {
171            counters.remove(location);
172        }
173    }
174
175    /**
176     * Obtains the set of chunk locations that have already been loaded
177     *
178     * @return the set of loaded chunk locations
179     */
180    public Set<ChunkLocation> keys() {
181        synchronized (counters) {
182            return counters.keySet();
183        }
184    }
185
186    /**
187     * Obtains the chunks that have been loaded into memory
188     *
189     * @return the collection of loaded in-memory chunks
190     */
191    public Collection<TridentChunk> values() {
192        Collection<TridentChunk> chunks = Lists.newArrayList();
193        synchronized (counters) {
194            counters.values().stream().forEach(c -> chunks.add(c.unwrap()));
195        }
196        return chunks;
197    }
198
199    /**
200     * Obtains the amount of loaded chunks
201     *
202     * @return the amount of loaded chunks
203     */
204    public int size() {
205        synchronized (counters) {
206            return counters.size();
207        }
208    }
209}