/*********************************************************************************** * Copyright (c) 2015 /// Project SWG /// www.projectswg.com * * * * ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on * * July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. * * Our goal is to create an emulator which will provide a server for players to * * continue playing a game similar to the one they used to play. We are basing * * it on the final publish of the game prior to end-game events. * * * * This file is part of Holocore. * * * * -------------------------------------------------------------------------------- * * * * Holocore is free software: you can redistribute it and/or modify * * it under the terms of the GNU Affero General Public License as * * published by the Free Software Foundation, either version 3 of the * * License, or (at your option) any later version. * * * * Holocore is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Affero General Public License for more details. * * * * You should have received a copy of the GNU Affero General Public License * * along with Holocore. If not, see . * * * ***********************************************************************************/ package resources.collections; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import network.packets.swg.zone.baselines.Baseline.BaselineType; import resources.network.DeltaBuilder; import resources.network.BaselineBuilder.Encodable; import resources.objects.SWGObject; import resources.player.PlayerState; import utilities.Encoder; import utilities.Encoder.StringType; public class SWGMap extends AbstractMap implements Encodable { private static final long serialVersionUID = 1L; private BaselineType baseline; private int view; private int updateType; private transient int updateCount; private int dataSize; private StringType strType = StringType.UNSPECIFIED; /* * Map which will contain all the byte data. A map is used here because it allows the encode method to not have to guess the ByteBuffer size. Doing it this way will * also allow all the data to be pre-compiled for the list, so it can have a positive impact on large SWGList's. This means that only 1 ByteBuffer is being created, * and that is to just take the data from this map and put it all together! */ private Map data = new ConcurrentHashMap<>(); private Map map = new ConcurrentHashMap(); private Map deltas = new HashMap<>(); private int deltaSize; public SWGMap(BaselineType baseline, int view, int updateType) { this.baseline = baseline; this.view = view; this.updateType = updateType; } public SWGMap(BaselineType baseline, int view, int updateType, StringType strType) { this.baseline = baseline; this.view = view; this.updateType = updateType; this.strType = strType; } @Override public V get(Object key) { return map.get(key); } @Override public V put(K key, V value) { updateCount++; V old = map.put(key, value); if (old != null) { removeData(key); } addData(key, value, (byte) 0); return old; } @Override public V remove(Object key) { updateCount++; V old = map.remove(key); removeData(key); return old; } @Override public Set keySet() { return map.keySet(); } @Override public Set> entrySet() { return map.entrySet(); } @Override public int size() { return map.size(); } @Override public boolean containsValue(Object value) { return map.containsValue(value); } @Override public boolean containsKey(Object key) { return map.containsKey(key); } /** * Sends a delta and updates the data, notifying clients of the changed item in this map. Use this if you are not adding or removing the element * and just simply changing it. Should be used after changing values for an item in this map. * @param key Associated key with the value that was modified. * @param parent The parent of this map, who the map is bound to in order to send the delta */ public void update(Object key, SWGObject parent) { updateCount++; removeDataSize(key); // remove the size of the prior data because not all encodables are fixed sizes (ie some have strings inside them) addData(key, map.get(key), (byte) 2); sendDeltaMessage(parent); } @Override public byte[] encode() { int size = map.size(); if (size == 0) { return new byte[8]; } ByteBuffer buffer = ByteBuffer.allocate(8 + dataSize + data.size()).order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(size); buffer.putInt(updateCount); if (data.size() != size) { // Data got out of sync with the map, so lets clean that up! clearAllData(); for (Entry entry : map.entrySet()) { addData(entry.getKey(), entry.getValue(), (byte) 0); } } for (byte[] bytes : data.values()) { buffer.put((byte) 0); buffer.put(bytes); } return buffer.array(); } public void sendDeltaMessage(SWGObject target) { if (!(deltas.size() > 0)) return; if (target.getOwner() == null || target.getOwner().getPlayerState() != PlayerState.ZONED_IN) { clearDeltaQueue(); return; } DeltaBuilder builder = new DeltaBuilder(target, baseline, view, updateType, getDeltaData()); builder.send(); // Clear the queue since the delta has been sent to observers through the builder clearDeltaQueue(); } public void clearDeltaQueue() { deltas.clear(); deltaSize = 0; } private byte[] getDeltaData() { ByteBuffer buffer = ByteBuffer.allocate(8 + deltaSize).order(ByteOrder.LITTLE_ENDIAN); buffer.putInt(deltas.size()); buffer.putInt(updateCount); for (byte[] data : deltas.values()) { buffer.put(data); } return buffer.array(); } private void createDeltaData(Object key, byte[] delta, byte update) { synchronized(deltas) { byte[] combindedUpdate = new byte[delta.length + 1]; combindedUpdate[0] = update; System.arraycopy(delta, 0, combindedUpdate, 1, delta.length); if (deltas.containsKey(key)) { deltaSize -= deltas.remove(key).length; } deltaSize += delta.length + 1; deltas.put(key, combindedUpdate); } } private void clearAllData() { dataSize = 0; data.clear(); clearDeltaQueue(); } private void addData(Object key, Object value, byte update) { byte[] encodedKey = Encoder.encode(key, strType); byte[] encodedValue = Encoder.encode(value, strType); byte[] encodedData = new byte[encodedKey.length + encodedValue.length]; System.arraycopy(encodedKey, 0, encodedData, 0, encodedKey.length); System.arraycopy(encodedValue, 0, encodedData, encodedKey.length, encodedValue.length); data.put(key, encodedData); dataSize += encodedData.length; createDeltaData(key, encodedData, update); } // Removes Key and its Value size of data and removes it from the data map private void removeData(Object key) { byte[] bytes = data.remove(key); if (bytes == null) { System.err.println("[SWGMap] Could not remove key as it wasn't in the data map: " + key); return; } dataSize -= bytes.length; createDeltaData(key, bytes, (byte) 1); } // Removes Key and its Value size of data without removing it from the data map private void removeDataSize(Object key) { dataSize -= data.get(key).length; } }