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}