-
Notifications
You must be signed in to change notification settings - Fork 1
3.Client Server communication
GAPS is a Java / JavaScript application that relies heavily and exclusively on bidirectional websocket communication between client and server. Client is usually, but not limited to, a browser. Any language or application correctly implementing the server API should be able to communicate with the server.
In order to do this, GAPS exposes a message API that has a well-defined structure. Location of the API endpoint is
ws:gaps_deploy_location/controller
defined in app.gaps.js file, located at gaps/src/main/webapp/scripts/app.gaps.js
This can be modified by altering class com.andreistraut.gaps.controller.Controller, modifying @ServerEndpoint("/controller") annotation
Communication model is asynchronous, based on request-response-notification. A request is a JSON object with the following fields:
- callback_id (int): id of the request, should be unique (determined and handled by the client). All responses from the server pertaining to this request will reference this id
-
type (string): the type of the request to process. Needed by the server in order to know what needs to be done with a request. As of 20/04/2015, the following message types are implemented:
- GetGraph - initializes and returns a JIT Infovis graph
- ComputePaths - computes an initial population of random paths for the previously generated graph
- Evolve - evolves the previously computed paths until a stop condition is reached (full algorithm run or a certain time without any improvements in results)
- Compare - compares the algorithm results against an array of other path search methods
- data (JSON object): the necessary data specific for the request to complete
Server responses also have a well-defined format:
- callback_id (int): references the callback_id for the request that was sent
- description (string): short description of the response status
- status (int): response status, follows HTTP status codes
- isEnded (boolean): whether the request has been fully processed and the current response is the last from the server pertaining to the sent request. If false, current response is only an update, and probably more responses with the same callback_id will following
- data (JSON object): specific data for each response type
Execution flow starts when a connection is established. Client sends an open connection request, and the server sends an acknowledgement message. If all is successful, the message is the following:
{
callback_id: 0
data: null
description: "Connection Established"
isEnded: true
status: 200
}
From here, the user starts setting the graph generation parameters, and when all is done, a graph generation message is sent to the server, with the following structure (specific values depend on user needs):
{
callback_id: 1,
type: "GetGraph",
data: {
maximumEdgeWeight: 100,
minimumEdgeWeight: 1,
numberOfEdges: 100,
numberOfEdgesMax: 1000,
numberOfNodes: 30,
numberOfNodesMax: 1000
}
}
When this completes, the client will render the graph, and display the statistics for the graph.
The following step is calculating the initial paths that will be used to seed the genetic algorithm (they don't need to be optimal, only valid. Optimization is the genetic algorithm's job). For simplicity reasons, the request structure is the same for compute path, evolve, and compare statistics request, also only necessary parameters are used internally for each one. The request looks like:
{
callback_id: 2,
type: "ComputePaths",
data: {
comparePaths: 5,
destinationNode: 29,
numberOfEvolutions: 10000,
numberOfPaths: 100,
sourceNode: 0,
stopConditionPercent: 100
}
}
When the server receives this request, it starts computing paths, until the maximum specified number is reached or no other paths are found. As long as this goes on, the server will keep notifying updates:
{
callback_id: 2,
description: "Ok",
isEnded: false,
status: 200,
data: {
path: Array[29]
}
}
When the request is fully processed, the paths along with their statistics are calculated and rendered.
This is where the genetic algorithm comes into play. The client sends an evolution request (see structure above), and the server starts mutating, crossing and selecting the initial paths until a solution is reached or no further evolution occurs.
As long as the paths keep evolving, the server will notify any changes. Even if the paths do not update, the server will still notify at every 10% of the algorithm stage:
{
callback_id: 3,
description: "Ok",
isEnded: false,
status: 200,
data: {
bestChromosome: {
age: 0
cost: 530
fitness: 99470
isLegal: true
isSelectedForNextGeneration: true
nodeFrom: Object
nodeTo: Object
path: Array[11]
},
worstChromosome: {},
endAverageCost: 10,
endAverageFitness: 1989.4,
endBestCost: 530,
endBestFitness: 99470,
endPopulationSize: 50,
evolutionStage: 362,
startAverageCost: 0,
startAverageFitness: 0.0005,
startBestCost: 606,
startBestFitness: 99394,
startPopulationSize: 50,
timeStamp: "10:03:51:040"
}
}
When the request is completed, the evolution statistics will be displayed
The final step is results compare, where the same request is sent to the server. The server calculates the comparison paths, and sends updates while they are being processed:
{
callback_id: 4,
description: "Ok",
isEnded: false,
status: 200,
data: {
age: 0,
cost: 151,
fitness: 99849,
isLegal: true,
isSelectedForNextGeneration: false,
nodeFrom: {},
nodeTo: {},
path: Array[4]
}
}
When processing finishes, a final message is sent to notify of this, and the comparison statistics are being displayed. From here, the flow can re-start by either computing a new graph and starting from scratch, or re-evolving the paths