I am trying to develop a Web-Client based Online game using GWT.
The game mechanics work quite fine by now and I’d really like to go on with my next step in my development plan, but now I am stuck with a simple encoding/decoding function I created to store my data for the client into a string.
The steps I took are not that complicated. During runtime, my server creates “ImageStates” of the game Objects to be drawn on a Canvas on my client. Each ImageState contains a number of Layers, which contain Details of what has to be drawn where at what specific time and so on.
I tried ObjectInput/OutputStream, combined with GZip-Compression and ByteInputStreams, but I could not find any GWT implementation of the first two so I had to think about another solution and came across gwt-lzma.
My goal is to encode those details into a single string, compress it on the server, send the compressed Data to the client, decompress and decode it there so it can be “read” by the client. To do so, I created an encoding and a decoding function:
The encoder:
public static String encodeImageStateContainer(HashMap<String,HashMap<Long,ImageState>> imageStateContainer){
StringBuilder mainStringBuilder = new StringBuilder();
Iterator<Entry<String,HashMap<Long,ImageState>>> imageStateContainerIterator = imageStateContainer.entrySet().iterator();
while(imageStateContainerIterator.hasNext()){
Entry<String,HashMap<Long,ImageState>> tempImageStateManagerMetadata = imageStateContainerIterator.next();
HashMap<Long,ImageState> tempImageStateManager = tempImageStateManagerMetadata.getValue();
if(tempImageStateManager.size() > 0){
if(mainStringBuilder.length() > 0){
mainStringBuilder.append('}'); //Divisor between Image State Managers
}
mainStringBuilder.append(tempImageStateManagerMetadata.getKey());
mainStringBuilder.append('|'); //Divisor between Name and following Data
StringBuilder subOneStringBuilder = new StringBuilder();
Iterator<Entry<Long,ImageState>> tempImageStateManagerIterator = tempImageStateManager.entrySet().iterator();
while(tempImageStateManagerIterator.hasNext()){
Entry<Long,ImageState> tempImageStateMetaData = tempImageStateManagerIterator.next();
if(subOneStringBuilder.length() > 0){
subOneStringBuilder.append(')'); //Divisor between Image Objects
}
subOneStringBuilder.append(tempImageStateMetaData.getKey());
subOneStringBuilder.append('-'); //Divisor between Object Id and Image State Data
StringBuilder subTwoStringBuilder = new StringBuilder();
ImageState tempImageState = tempImageStateMetaData.getValue();
subOneStringBuilder.append(tempImageState.getFirstFrameId()); //1. => First Frame Id
subOneStringBuilder.append(';'); //Divisor between Image State data types
subOneStringBuilder.append(tempImageState.getFramesPerLayer()); //2. => Total Frame Count
subOneStringBuilder.append(';');
subOneStringBuilder.append(tempImageState.getMinumimScaleFactor()); //3. => Minimum Scale Factor
subOneStringBuilder.append(';');
ImageStateLayer[] tempImageStateLayers = tempImageState.getImageStateLayers();
for(int layerId = 0; layerId < tempImageStateLayers.length; ++layerId){
if(subTwoStringBuilder.length() > 0){
subTwoStringBuilder.append('/'); //Divisor between Image State Layers
}
//Frame Arrays
String[] imageNativePath = tempImageStateLayers[layerId].getImageNativePath();
short[] offsetX = tempImageStateLayers[layerId].getOffsetX();
short[] offsetY = tempImageStateLayers[layerId].getOffsetY();
short[] orientation = tempImageStateLayers[layerId].getOrientation();
//Finalization Arrays
byte[] imagePathChange = tempImageStateLayers[layerId].getImagePathChange();
byte[] offsetXChange = tempImageStateLayers[layerId].getOffsetXChange();
byte[] offsetYChange = tempImageStateLayers[layerId].getOffsetYChange();
byte[] orientationChange = tempImageStateLayers[layerId].getOrientationChange();
//Image Path Data
StringBuilder subThreeStrignBuilder = new StringBuilder();
for(int imagePathId = 0; imagePathId < imageNativePath.length; ++imagePathId){
if(subThreeStrignBuilder.length() > 0){
subThreeStrignBuilder.append('#'); //Divisor between Frame Data Sets
}
subThreeStrignBuilder.append(imageNativePath[imagePathId]);
subThreeStrignBuilder.append(','); //Divisor between Frame Data Set Data
subThreeStrignBuilder.append(imagePathChange[imagePathId]);
}
subTwoStringBuilder.append(subThreeStrignBuilder.toString());
subTwoStringBuilder.append('['); //Divisor between Frame Data Types
//OffsetX Data
subThreeStrignBuilder = new StringBuilder();
for(int offsetXId = 0; offsetXId < offsetX.length; ++offsetXId){
if(subThreeStrignBuilder.length() > 0){
subThreeStrignBuilder.append('#'); //Divisor between Frame Data Sets
}
subThreeStrignBuilder.append(offsetX[offsetXId]);
subThreeStrignBuilder.append(','); //Divisor between Frame Data Set Data
subThreeStrignBuilder.append(offsetXChange[offsetXId]);
}
subTwoStringBuilder.append(subThreeStrignBuilder.toString());
subTwoStringBuilder.append('['); //Divisor between Frame Data Types
//OffsetY Data
subThreeStrignBuilder = new StringBuilder();
for(int offsetYId = 0; offsetYId < offsetY.length; ++offsetYId){
if(subThreeStrignBuilder.length() > 0){
subThreeStrignBuilder.append('#'); //Divisor between Frame Data Sets
}
subThreeStrignBuilder.append(offsetY[offsetYId]);
subThreeStrignBuilder.append(','); //Divisor between Frame Data Set Data
subThreeStrignBuilder.append(offsetYChange[offsetYId]);
}
subTwoStringBuilder.append(subThreeStrignBuilder.toString());
subTwoStringBuilder.append('['); //Divisor between Frame Data Types
//Orientation Data
subThreeStrignBuilder = new StringBuilder();
for(int orientationId = 0; orientationId < orientation.length; ++orientationId){
if(subThreeStrignBuilder.length() > 0){
subThreeStrignBuilder.append('#'); //Divisor between Frame Data Sets
}
subThreeStrignBuilder.append(orientation[orientationId]);
subThreeStrignBuilder.append(','); //Divisor between Frame Data Set Data
subThreeStrignBuilder.append(orientationChange[orientationId]);
}
subTwoStringBuilder.append(subThreeStrignBuilder.toString());
}
subOneStringBuilder.append(subTwoStringBuilder.toString());
}
mainStringBuilder.append(subOneStringBuilder.toString());
}
}
return mainStringBuilder.toString();
}
The decoder:
public static HashMap<String,HashMap<Long,ImageState>> decodeImageStateContainer(String data){
String[] imageStateManagerArray = data.split("\\}");
HashMap<String,HashMap<Long,ImageState>> imageStateManagerContainer = new HashMap<String,HashMap<Long,ImageState>>(imageStateManagerArray.length);
for(int managerId = 0; managerId < imageStateManagerArray.length; ++managerId){
String[] tempImageStateData = imageStateManagerArray[managerId].split("\\|");
HashMap<Long,ImageState> tempImageStateManager = new HashMap<Long,ImageState>();
imageStateManagerContainer.put(tempImageStateData[0], tempImageStateManager);
String[] tempImageStateManagerObjects = tempImageStateData[1].split("\\)");
for(int objectId = 0; objectId < tempImageStateManagerObjects.length; ++objectId){
String[] tempImageObjectData = tempImageStateManagerObjects[objectId].split("\\-");
long imageObjectId = Long.parseLong(tempImageObjectData[0]);
String[] imageStateMetaData = tempImageObjectData[1].split("\\;");
ImageState tempImageState = new ImageState(Integer.parseInt(imageStateMetaData[1]), Integer.parseInt(imageStateMetaData[0]), Integer.parseInt(imageStateMetaData[2]));
tempImageStateManager.put(imageObjectId, tempImageState);
String[] tempImageStateLayerMetaData = imageStateMetaData[3].split("\\/");
ImageStateLayer[] tempImageStateLayers = new ImageStateLayer[tempImageStateLayerMetaData.length];
for(int layerId = 0; layerId < tempImageStateLayerMetaData.length; ++layerId){
String[] layerElements = tempImageStateLayerMetaData[layerId].split("\\[");
String[] imageNativePathDetails = layerElements[0].split("\\#");
String[] offsetXDetails = layerElements[1].split("\\#");
String[] offsetYDetails = layerElements[2].split("\\#");
**String[] orientationDetails = layerElements[3].split("\\#");**
//Image Path Data
String[] imageNativePaths = new String[imageNativePathDetails.length];
byte[] imagePathChange = new byte[imageNativePathDetails.length];
for(int id = 0; id < imageNativePathDetails.length; ++id){
String[] imagePathDetailElements = imageNativePathDetails[id].split("\\,");
imageNativePaths[id] = imagePathDetailElements[0];
imagePathChange[id] = Byte.parseByte(imagePathDetailElements[1]);
}
//OffsetX Data
short[] offsetX = new short[offsetXDetails.length];
byte[] offsetXChange = new byte[offsetXDetails.length];
for(int id = 0; id < offsetXDetails.length; ++id){
String[] offsetXDetailElements = offsetXDetails[id].split("\\,");
offsetX[id] = Short.parseShort(offsetXDetailElements[0]);
offsetXChange[id] = Byte.parseByte(offsetXDetailElements[1]);
}
//OffsetY Data
short[] offsetY = new short[offsetYDetails.length];
byte[] offsetYChange = new byte[offsetYDetails.length];
for(int id = 0; id < offsetYDetails.length; ++id){
String[] offsetYDetailElements = offsetYDetails[id].split("\\,");
offsetY[id] = Short.parseShort(offsetYDetailElements[0]);
offsetYChange[id] = Byte.parseByte(offsetYDetailElements[1]);
}
//Orientation Data
short[] orientation = new short[orientationDetails.length];
byte[] orientationChange = new byte[orientationDetails.length];
for(int id = 0; id < orientationDetails.length; ++id){
String[] orientationDetailElements = orientationDetails[id].split("\\,");
orientation[id] = Short.parseShort(orientationDetailElements[0]);
orientationChange[id] = Byte.parseByte(orientationDetailElements[1]);
}
//Create the Layer and Add it to the Array
tempImageStateLayers[layerId] = new ImageStateLayer(imageNativePaths,new short[][]{offsetX,offsetY,orientation}, new byte[][]{imagePathChange,offsetXChange,offsetYChange,orientationChange});
}
//Connect the Reference to the layers with the Image State
tempImageState.setImageStateLayers(tempImageStateLayers);
}
}
return imageStateManagerContainer;
}
Now my problem: If I encode it on the server and decode it directly afterwards It takes about three cycles of Encoding until I get an IndexOutofBoundsException at this specific position in the decoder, the last line is the line where the error seems to be according to stacktrace:
for(int layerId = 0; layerId < tempImageStateLayerMetaData.length; ++layerId){
String[] layerElements = tempImageStateLayerMetaData[layerId].split("\\[");
String[] imageNativePathDetails = layerElements[0].split("\\#");
String[] offsetXDetails = layerElements[1].split("\\#");
String[] offsetYDetails = layerElements[2].split("\\#");
String[] orientationDetails = layerElements[3].split("\\#");
I would not say I’m an expert, but after testing the encoder I can definitely say that the string generated by it is always valid, the string at which I get this error is complete as well. There must be something wrong with my decoding function, but I have no idea what it could be.
As I said, the first three cycles are encoded and decoded properly and the data to encode did not change dramatically. There is no way that the encoded string could lead to having a String array smaller than 4 elements. Basically, this error can’t exist as far as I understand my code.
I think it could be some kind of memory allocation problem that prevents the string from being split properly, but thats just an idea of a clueless programmer.
Any help is deeply appreciated!
I found the solution to my problem:
While encoding, I was printing Numeric types to my string. While selecting my Seperator symbols I didn’t take negative values, resulting in a ‘-‘ before the digit into consideration, which led to an uncontrolled splitting of my String when I tryed to split by ‘-‘. I replaced the minus with another character and everything works fine now!