package nova.microblock.micro; import nova.core.block.Block; import nova.core.block.Stateful; import nova.core.component.Component; import nova.core.component.Updater; import nova.core.component.transform.BlockTransform; import nova.core.network.NetworkException; import nova.core.network.Packet; import nova.core.network.Syncable; import nova.core.retention.Data; import nova.core.retention.Storable; import nova.core.util.Direction; import nova.core.util.math.MathUtil; import nova.core.util.math.Vector3DUtil; import nova.core.util.shape.Cuboid; import nova.microblock.NovaMicroblock; import nova.microblock.common.BlockComponent; import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; /** * A component added to microblocks * @author Calclavia */ public class MicroblockContainer extends BlockComponent implements Syncable, Storable, Updater { /** * The amount of subdivisions of the microblock. * Must be 2^n */ //TOD: Make this variable, or configurable? public static final int subdivision = 8; private final String saveID = "microblockContainer"; /** * A sparse block map from (0,0) to (subdivision, subdivision) coordinates * of all the microblocks. */ private final Map<Vector3D, Microblock> blockMap = new HashMap<>(); public MicroblockContainer(Block block) { super(block); } public static Vector3D centerPosition() { return Vector3DUtil.ONE.scalarMultiply((subdivision - 1) / 2); } public static Vector3D sidePosition(Direction direction) { return Vector3DUtil.floor( direction.toVector() .add(Vector3DUtil.ONE) .scalarMultiply(0.5) .scalarMultiply(subdivision - 1) ); } /** * Gets a collection of all microblocks. */ public Collection<Microblock> microblocks() { return blockMap.values(); } /** * Gets a collection of all components of the same type in all microblocks. * @param componentClass The component class * @param <C> The component type * @return Gets a stream of components in the microblocks */ public <C extends Component> Stream<C> microblocks(Class<C> componentClass) { return stream() .map(microblock -> microblock.block.components.getOp(componentClass)) .filter(Optional::isPresent) .map(Optional::get); } public Stream<Microblock> stream() { return new HashSet<>(blockMap.values()).stream(); } /** * Puts a new microblock into this container. */ public boolean putNew(Vector3D localPos, Microblock microblock) { if (put(localPos, microblock)) { //Invoke load event microblock.block.events.publish(new Stateful.LoadEvent()); //Invoke neighbor change event microblocks().stream() .filter(m -> m != microblock) .forEach(m -> m.block.events.publish(new Microblock.MicroblockChangeEvent(Optional.of(localPos)))); block.world().markChange(block.position()); return true; } return false; } /** * Puts a microblock directly into this container. * No events will be invoked. */ public boolean put(Vector3D localPos, Microblock microblock) { assert new Cuboid(0, 0, 0, subdivision, subdivision, subdivision).intersects(localPos); if (!has(localPos)) { //Place microblock microblock.containers.add(this); microblock.position = localPos; blockMap.put(localPos, microblock); //Inject components NovaMicroblock.instance.componentInjection.injectToContained(microblock.block, block); NovaMicroblock.instance.componentInjection.injectToContainer(microblock.block, block); //If this is not the first microblock (first one already sent via default packet) if (NovaMicroblock.instance.network.isServer() && microblocks().size() > 1) { NovaMicroblock.instance.network.sync(block); } return true; } return false; } public boolean remove(Vector3D localPos) { if (has(localPos)) { get(localPos).get().block.events.publish(new Stateful.UnloadEvent()); blockMap.remove(localPos); if (microblocks().size() > 0 && NovaMicroblock.instance.network.isServer()) { NovaMicroblock.instance.network.sync(block); } //Invoke neighbor change event microblocks().stream() .forEach(m -> m.block.events.publish(new Microblock.MicroblockChangeEvent(Optional.of(localPos)))); block.world().markChange(block.position()); return true; } return false; } /** * Gets a microblock based on the slot. * @param side The side of the microblock * @return The microblock that is occupying this specific side. */ //TODO: Change to region public Optional<Microblock> get(Direction side) { return get(sidePosition(side)); } public boolean has(Vector3D localPos) { return blockMap.containsKey(localPos); } /** * Gets the microblock at a specific internal position. * @param localPos Te local position within the microblock space * @return The optional microblock. */ public Optional<Microblock> get(Vector3D localPos) { return Optional.ofNullable(blockMap.get(localPos)); } /** * Gets a single microblock that converts a specific region within the microblock space. * @param region A region in the microblock space * @return A single microblock if that microblock occupies the entire region. */ public Optional<Microblock> get(Cuboid region) { //NO-OP. TODO: IMPLEMENT return Optional.empty(); } /** * @return A man of local positions to their microblocks. */ public Map<Vector3D, Microblock> map() { return blockMap; } @Override public void update(double deltaTime) { stream() .filter(m -> m instanceof Updater) .map(m -> (Updater) m) .forEach(m -> m.update(deltaTime)); } @Override public void read(Packet packet) { //Description Packet if (packet.getID() == 0) { //Reset microblocks blockMap.clear(); //Reset container components new HashSet<>(block.components()) .stream() .filter(c -> !(c instanceof BlockTransform) && !(c instanceof MicroblockContainer)) .forEach(block.components::remove); int size = packet.readInt(); for (int i = 0; i < size; i++) { Vector3D microPos = idToPos(packet.readInt()); String microID = packet.readString(); //Find microblock registered withPriority such ID NovaMicroblock.MicroblockInjectFactory injectionFactory = NovaMicroblock.instance.containedIDToFactory.get(microID); Block microblock = injectionFactory.containedFactory.build(); put(microPos, microblock.components.get(Microblock.class)); if (microblock instanceof Syncable) { ((Syncable) microblock).read(packet); } } } else { throw new NetworkException("Microblock container reading an invalid packet ID: " + packet.getID() + ". This error may be due to an attempt to send microblock data without passing the microblock component as the packet sender."); } } @Override public void write(Packet packet) { //Description Packet if (packet.getID() == 0) { packet.writeInt(microblocks().size()); //Write all microblocks over map().forEach((k, v) -> { packet.write(posToID(k)); packet.writeString(v.block.getID()); if (v.block instanceof Syncable) { ((Syncable) v.block).write(packet); } }); } else { throw new NetworkException("Microblock container writing an invalid packet ID: " + packet.getID() + ". This error may be due to an attempt to send microblock data without passing the microblock component as the packet sender.\""); } } @Override public void load(Data data) { blockMap.clear(); ((Data) data.get(saveID)).forEach((k, v) -> { Block savedBlock = (Block) Data.unserialize((Data) v); Microblock microblock = savedBlock.components.get(Microblock.class); put(idToPos(Integer.parseInt(k)), microblock); microblock.block.events.publish(new Stateful.LoadEvent()); }); } @Override public void save(Data data) { Data microblockData = new Data(); map().forEach((k, v) -> { if (v.block instanceof Storable) { microblockData.put(posToID(k) + "", v.block); } } ); data.put(saveID, microblockData); } public int posToID(Vector3D pos) { int shift = MathUtil.log(subdivision, 2); return ((int) pos.getX() << (shift * 2)) | ((int) pos.getY() << (shift)) | (int) pos.getZ(); } public Vector3D idToPos(int id) { int shift = MathUtil.log(subdivision, 2); int propogateReference = 1 << (shift - 1); int ones = propogateReference | (propogateReference - 1); int z = id & ones; int y = (id & (ones << shift)) >> shift; int x = id >> (shift * 2); return new Vector3D(x, y, z); } }