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.world.change; 018 019import net.tridentsdk.base.Position; 020import net.tridentsdk.base.Substance; 021import net.tridentsdk.server.data.RecordBuilder; 022import net.tridentsdk.server.packets.play.out.PacketPlayOutMultiBlockChange; 023import net.tridentsdk.server.player.TridentPlayer; 024import net.tridentsdk.server.world.TridentChunk; 025import net.tridentsdk.server.world.WorldUtils; 026import net.tridentsdk.world.ChunkLocation; 027import net.tridentsdk.world.MassChange; 028import net.tridentsdk.world.World; 029 030import java.util.*; 031import java.util.concurrent.ConcurrentLinkedQueue; 032 033/** 034 * A MassChange that is thread-safe, unlike the default 035 * 036 * @author The TridentSDK Team 037 */ 038public class ThreadSafeChange implements MassChange { 039 private final World world; 040 private volatile boolean committed = false; 041 private Queue<BlockChange> changes = new ConcurrentLinkedQueue<>(); 042 043 public ThreadSafeChange(World world) { 044 this.world = world; 045 } 046 047 @Override 048 public void setBlock(int x, int y, int z, int id) throws IllegalStateException { 049 setBlock(x, y, z, id, (byte) 0); 050 } 051 052 @Override 053 public void setBlock(int x, int y, int z, Substance substance) throws IllegalStateException { 054 setBlock(x, y, z, substance.id(), (byte) 0); 055 } 056 057 @Override 058 public void setBlock(int x, int y, int z, Substance substance, byte data) throws IllegalStateException { 059 setBlock(x, y, z, substance.id(), data); 060 } 061 062 @Override 063 public void setBlock(int x, int y, int z, int id, byte data) throws IllegalStateException { 064 // real function 065 066 if(committed) { 067 throw new IllegalArgumentException("Change has already been committed."); 068 } 069 changes.add(new BlockChange(x, y, z, (byte) id, data)); 070 } 071 072 073 @Override 074 public void setBlock(Position coords, int id) throws IllegalStateException { 075 setBlock(coords, id, (byte) 0); 076 } 077 078 @Override 079 public void setBlock(Position coords, int id, byte data) throws IllegalArgumentException, 080 IllegalStateException { 081 if (coords.world().equals(this.world)) { 082 setBlock((int) Math.round(coords.x()), 083 (int) Math.round(coords.y()), 084 (int) Math.round(coords.z()), id, data); 085 } else { 086 throw new IllegalArgumentException("PositionWritable provided do not match the world that this change is for"); 087 } 088 } 089 090 @Override 091 public void setBlock(Position coords, Substance substance) throws IllegalArgumentException, 092 IllegalStateException { 093 setBlock(coords, substance, (byte) 0); 094 } 095 096 @Override 097 public void setBlock(Position coords, Substance substance, byte data) throws IllegalArgumentException, 098 IllegalStateException { 099 setBlock(coords, substance.id(), data); 100 } 101 102 @Override 103 public boolean commitChanges() throws IllegalStateException { 104 if(committed) { 105 throw new IllegalArgumentException("Change has already been committed."); 106 } 107 108 Map<ChunkLocation, List<BlockChange>> map = new HashMap<>(); 109 110 for(BlockChange change : changes) { 111 ChunkLocation location = WorldUtils.chunkLocation(change.x(), change.z()); 112 List<BlockChange> updatedChanges = map.get(location); 113 114 if(updatedChanges == null) { 115 updatedChanges = new ArrayList<>(); 116 } 117 118 updatedChanges.add(change); 119 map.put(location, updatedChanges); 120 } 121 122 for(Map.Entry<ChunkLocation, List<BlockChange>> entry : map.entrySet()) { 123 List<BlockChange> changes = entry.getValue(); 124 PacketPlayOutMultiBlockChange packet = new PacketPlayOutMultiBlockChange(); 125 RecordBuilder[] records = new RecordBuilder[changes.size()]; 126 TridentChunk chunk = (TridentChunk) world.chunkAt(entry.getKey(), true); 127 128 for (int i = 0; i < records.length; i++) { 129 BlockChange change = changes.get(i); 130 131 records[i] = new RecordBuilder().setBlockId(change.id()) 132 .setX((byte) change.x()) 133 .setY((byte) change.y()) 134 .setZ((byte) change.z()) 135 .setData(change.data()); 136 chunk.setAt(change.x(), change.y(), change.z(), Substance.fromId(change.id()), 137 change.data(), (byte) 255, (byte) 15); 138 } 139 140 packet.set("records", records).set("chunkLocation", entry.getKey()); 141 TridentPlayer.sendAll(packet); 142 } 143 144 return true; 145 } 146}