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}