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.Sets;
020import net.tridentsdk.Trident;
021import net.tridentsdk.base.Position;
022import net.tridentsdk.config.ConfigSection;
023import net.tridentsdk.docs.Policy;
024import net.tridentsdk.server.packets.play.out.PacketPlayOutChunkData;
025import net.tridentsdk.server.player.PlayerConnection;
026import net.tridentsdk.server.player.TridentPlayer;
027import net.tridentsdk.server.world.TridentChunk;
028import net.tridentsdk.server.world.TridentWorld;
029import net.tridentsdk.world.ChunkLocation;
030
031import javax.annotation.concurrent.GuardedBy;
032import java.util.HashSet;
033import java.util.Iterator;
034import java.util.LinkedList;
035import java.util.Set;
036
037/**
038 * The set of chunk locations and management methods for a player connected to the server
039 *
040 * @author The TridentSDK Team
041 */
042public class ChunkLocationSet {
043    private static final ConfigSection tridentCfg = Trident.config().getConfigSection("performance");
044    private static final int MAX_CHUNKS = tridentCfg.getInt("max-chunks-player", 441);
045    private static final int CLEAN_ITERATIONS = tridentCfg.getInt("chunk-clean-iterations-player", 2);
046
047    @GuardedBy("knownChunks")
048    private final HashSet<ChunkLocation> knownChunks = Sets.newHashSet();
049    private final TridentPlayer player;
050
051    /**
052     * Creates a new chunk set for the given player
053     *
054     * @param player the player to associate the chunks with
055     */
056    public ChunkLocationSet(TridentPlayer player) {
057        this.player = player;
058    }
059
060    /**
061     * Clears the chunks that are not used within the specified view distance
062     *
063     * @param distance the distance of chunks of which to retain
064     */
065    public void clean(int distance) {
066        synchronized (knownChunks) {
067            for (int i = 0; i < CLEAN_ITERATIONS; i++) {
068                int size = knownChunks.size();
069                if (size > MAX_CHUNKS) {
070                    clean0(distance - i);
071                }
072            }
073        }
074    }
075
076    @Policy("holds knownChunks")
077    public void clean0(int viewDist) {
078        Position pos = player.position();
079        int x = (int) pos.x() / 16;
080        int z = (int) pos.z() / 16;
081
082        for (Iterator<ChunkLocation> locs = knownChunks.iterator(); locs.hasNext(); ) {
083            ChunkLocation location = locs.next();
084            int cx = location.x();
085            int cz = location.z();
086
087            int abs = Math.max(cx, x) - Math.min(cx, x);
088            int abs1 = Math.max(cz, z) - Math.min(cz, z);
089
090            if (abs >= viewDist || abs1 >= viewDist) {
091                player.connection().sendPacket(new PacketPlayOutChunkData(new byte[0], location, true, (short) 0));
092                locs.remove();
093                world().chunkHandler().apply(location, CRefCounter::releaseStrong);
094            }
095        }
096    }
097
098    /**
099     * Updates the chunks the player does not currently have within the given view distance
100     *
101     * @param viewDistance the diameter of the circle which to send chunks that the player currently does not possess as
102     *                     listed in this set
103     */
104    public void update(int viewDistance) {
105        int centX = (int) Math.floor(player.position().x()) >> 4;
106        int centZ = (int) Math.floor(player.position().z()) >> 4;
107
108        LinkedList<PacketPlayOutChunkData> chunks = new LinkedList<>();
109        PlayerConnection connection = player.connection();
110
111        synchronized (knownChunks) {
112            for (int x = centX - viewDistance / 2; x <= centX + viewDistance / 2; x += 1) {
113                for (int z = centZ - viewDistance / 2; z <= centZ + viewDistance / 2; z += 1) {
114                    TridentChunk center = null;
115                    for (int i = x - 1; i <= x + 1; i++) {
116                        for (int j = z - 1; j <= z + 1; j++) {
117                            ChunkLocation loc = ChunkLocation.create(i, j);
118                            if (knownChunks.contains(loc)) continue;
119
120                            TridentChunk chunk = world().chunkAt(loc, true);
121                            if (i == x && j == z) {
122                                center = chunk;
123                            }
124                        }
125                    }
126
127                    // if the player doesn't already know this chunk
128                    if (center != null) {
129                        ChunkLocation location = center.location();
130                        if (!knownChunks.add(location)) continue;
131                        world().chunkHandler().apply(location, CRefCounter::refStrong);
132
133                        chunks.addLast(center.asPacket());
134                    }
135                }
136            }
137
138            chunks.forEach(connection::sendPacket);
139        }
140    }
141
142    /**
143     * Correctly clears and releases the chunk references held by this set
144     */
145    public void clear() {
146        ChunkHandler handler = world().chunkHandler();
147        synchronized (knownChunks) {
148            handler.releaseReferences(this);
149            knownChunks.clear();
150        }
151    }
152
153    /**
154     * Obtains the chunks that are held in this location set
155     *
156     * @return the raw location set
157     */
158    @Policy("holds knownChunks")
159    public Set<ChunkLocation> locations() {
160        return this.knownChunks;
161    }
162
163    @Policy("world can change, do not cache")
164    private TridentWorld world() {
165        return (TridentWorld) player.world();
166    }
167}