Esta primeira atividade teve o foco de estudo da solução de integração WorldServer3D e SOAR, fornecida como base para o desenvolvimento proposto na segunda atividade.
Logo de início um primeiro ponto observado foi que a versão do WorldServer3D é diferente da utilizada para a primeira aula do curso; isso foi observado porque o cliente desenvolvido naquela aula deixou de funcionar, uma vez que se baseava no uso dos comando "showworldthings" e "getfullstatus3D" que na versão mais nova não estava disponíveis.
Análise do código do "DemoSOAR"
1. Uso da classe "NativeUtils" para resolver o problema do gerenciamento do código JNI
A classe que contém o método "main" é a "SimulationSOAR". Nesse método, primeiramente NativeUtils.setLibraryPath(".") é utilizado para indicar que as bibliotecas nativas deverão ser obtidas do diretório atual - parâmetro ".":
NativeUtils.setLibraryPath(".");
System.out.println("OS:"+System.getProperties().getProperty("os.name")+
" Architecture:"+System.getProperties().getProperty("os.arch"));
String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
String osArch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH);
Na sequência, dependendo do sistema operacional detectado - através dos comandos System.getProperty("os.name") e System.getProperty("os.arch") - o método NativeUtils.loadFileFromJar() é utilizado para carregar as bibliotecas SOAR correspondentes.
NativeUtils.loadFileFromJar() extrai o arquivo parâmetro de dentro do jar - a partir da InputStream obtida com NativeUtils.class.getResourceAsStream() - gravando como um arquivo separado dentro do diretório atual:
} else if(osName.contains("nix") || osName.contains("nux")){
if(osArch.contains("64")) {
System.out.println("Linux 64 bits");
NativeUtils.loadFileFromJar("/linux64/libSoar.so");
NativeUtils.loadFileFromJar("/linux64/libJava_sml_ClientInterface.so");
}
Por fim o conjunto de regras "soar-rules.soar" é também carregado:
NativeUtils.loadFileFromJar("/soar-rules.soar");
String soarRulesPath = "soar-rules.soar";
2. Observe que essa solução é estendida ao carregamento de arquivos com código SOAR, para uso do controlador. Como isso é implementado no Demo ?
Primeiramente o arquivo "soar-rules.soar" é extraído do .jar, via "NativeUtils.loadFileFromJar()". A referência para esse arquivo (seu nome) é passado no método "simulationTask.initializeCreatureAndSOAR()"; a classe "simulationTask" é a responsável pela execução ta tarefa dessa simulação, comandando a interação tanto com o WorldServer3D quanto com o SOAR.
Por fim a referência ao arquivo de regras é passada até a classe "SoarBridge" via seu construtor.
A classe "SoarBridge" cria e manipula um Kernel para uso do SML (Soar Markup Language); seu construtor faz as seguintes tarefas:
- Cria um Kernel usando "Kernel.CreateKernelInNewThread()"
- Cria um agente usando "kernel.CreateAgent()"
- Carrega no agente as regras de "soar-rules.soar", utilizando "agent.LoadProductions()"
- Ativa um SoarDebugger associado ao agente, utilizando "agent.SpawnDebugger()"
3. O loop principal de simulação do DemoSOAR também se encontra no método main. Explique seu funcionamento.
No método "SimulationSOAR.main()" a execução da simulação está dentro de um "while(true)"; nesse loop é chamado o método "simulationTask.runSimulation()" a cada ciclo.
Como mencionado anteriormente, a classe "SimulationTask()" é quem controla a execução da simulação, mantendo a conexão entre o "WorldServer3D" e o "Soar".
Para conectar-se ao WorldServer3D é utilizada a classe "WS3DProxy", que oferece os seguintes métodos:
- createCreature(): solicita a criação de uma nova criatura no WorldServer3D, retornando um objeto da classe "Creature".
- getCreature(): solicita uma referência para uma criatura já existente no WorldServer3D
- setWorld(): define qual objeto da classe "World" deve ser utilizado pelo proxy
- getWorld(): obtém uma objeto da classe "World", o qual permite a interação com o WorldServer3D
O restante da interface com "WorldServer3D" é obtido com as classes "Creature" e "World"; através dessas classes é possível manipular tanto as criaturas quando o mundo todo do WorldServer3D.
Já a conexão com o Soar é feita a partir da classe "SoarBridge" comentada anteriormente, essencialmente através das classes SML "Kernel" e "Agent".
O método "SimulationTask.prepareAndSetupCreatureToSimulation()" realiza os seguintes passos:
- Atualiza o status da criatura e prepara os outputs para o SOAR (os inputs para o agente) a partir das informações obtidas do WorldServer3D - método "SimulationTask.prepareAndSetupCreatureToSimulation()".
- Executa o agente via "soarBridge.runSimulation()".
- Processa as respostas do agente (as extensões criadas pelo agente no seu output-link) e envia os comandos correspondentes ao WorldServer3D - método "SimulationTask.processResponseComands()".
Detalhamento dos passos de "SimulationTask.prepareAndSetupCreatureToSimulation()"
a. Atualização do estado da criatura e uso do método "SimulationTask.prepareAndSetupCreatureToSimulation()"
O estado da criatura dentro do WorldServer3D é atualizado através do método "Creature.updateState()":
public synchronized Creature updateState() {
try {
logger.info("----------- updateThingsStatus -----------");
CommandUtility.updateStatus();
} catch (CommandExecException ex) {
java.util.logging.Logger.getLogger(WS3DProxy.class.getName()).log(Level.SEVERE, null, ex);
}
return this;
}
É utilizada a classe "CommandUtility()" a qual implementa a maioria dos comandos oferecidos pela interface do WorldServer3D; é utilizando o comando "getcreaturestate [creature_name] para obter o status da criatura, retornando as seguintes informações:
<creature_name> <creature_index> <X> <Y> <size> <pitch> <#-motorSys> <wheel> <speed> <fuel> <color> <camera> [<leaflets-list>] [<things-in-vision>]
A lista de objetos vistos - informação opcional <things-in-vision> - é utilizada para compor o atributo "Creature.thingsInVision".
Assim, dentro do método "SimulationTask.prepareAndSetupCreatureToSimulation()", primeiramente são obtidos os objetos dentro do campo de visão da criatura, através da chamada "Creature.getThingsInVision()". Todas essas informações são armazenadas em um objeto da classe "VisualSensor".
Adicionalmente é obtido o nível de combustível atual da criatura, via método "Creature.getFuel()". Essa informação é capturada num objeto da classe "FuelSensor".
Ambos objetos de sensores são capturados num objeto da classe "SimulationRobot" que é por fim passado para o "SoarBridge" via método "setupStackHolder()".
Esse método "SoarBridge.setupStackHolder()" é o método que repassa para o SOAR os dados obtidos do "WorldServer3D", utilizando métodos do pacote "sml" que manipulam elementos na memória de trabalho do agente; são usados os seguintes métodos:
- Agent.CreateIdWME(<elemento-pai>, <"nome-novo-elemento">): cria um novo elemento na memória de trabalho
- Agent.CreateFloatWME(<elemento-pai>, <"nome-novo-elemento">, <valor-novo-element>): cria um novo elemento FLOAT na memória de trabalho
É criada a seguinte estrutura de informação:
<s> ^input-link <il>
<il> ^CREATURE <c>
<c> ^MEMORY <cm>
^PARAMETERS <cp>
^POSITION <cpos>
^SENSOR <csens>
<cp> ^MINFUEL 400
^TIMESTAMP CURRENT-TIME
<cpos> ^X X
^Y Y
<csens> ^FUEL FUEL-LEVEL
^VISUAL <csvisual>
<csvisual> ^ENTITY <entity-1> ... <entity-n>
<entity-*> ^DISTANCE GEOMETRIC-DISTANCE
^X X1
^Y Y1
^X2 X2
^Y2 Y2
^TYPE CATEGORY
^NAME NAME
^COLOR COLOR
b. Execução do agente
Esta etapa é a mais simples, pois corresponde à chamada do método "Agent.RunSelfTilOutput()" que executa o agente até que ele produza uma saída (extensão em "output-link") ou se passem 15 ciclos Soar.
c. método "processResponseCommands()"
A primeira parte desse método é a execução de "SoarBridge.getReceivedCommands()": este método interfaceia com o agente via alguns métodos do pacote "sml":
- Agent.GetCommand(<number>): Devolve o identificador do comando "number";
- Identifier.GetCommandName(): Devolve o nome do comando; possíveis são "MOVE", "GET" ou "EAT".
- Identifier.GetParameterValue(<"parameter-name">): Devolve o valor da extensão parâmetro.
"SoarBridge.getReceivedCommands()" devolve um array de objetos "SoarCommand".
Pelo código os comandos são os seguintes - quando criados no output-link:
<s> ^output-link <ol>
<ol> ^MOVE <cmove>
<cmove> ^VelR VEL-RIGHT
^VelL VEL-LEFT
^Vel VEL-LINEAR
^x X
^y Y
<ol> ^GET <cget>
<cget> ^name THING-NAME-TO-GET
<ol> ^EAT <ceat>
<ceat> ^name THING-NAME-TO-EAT
Depois de processar os comandos criados no "output-link", a próxima etapa é enviá-los para o WorldServer3D, o que é feito com o uso da mesma classe "CommandUtility", já descrita anteriormente
No caso dos comando que podem ser recebidos do SOAR, o processamento é o seguinte:
MOVE - envia um comando "setgoTo", caso estejam disponíveis valores de "x,y"; envia um comando "setTurn", caso não estejam disponíveis valores de "x, y"
- GET - envia um comando "sackit", passando o nome do objeto a ser apanhado
- EAT - envia um comando "eatit", passando o nome do objeto a ser comido
4. Descrição do funcionamento em "soar-rules.soar"
Como não é possível executar o SoarDebugger como o DemoSOAR executado a partir do WebStart, é necessário executá-lo localmente, indicando onde o SoarDebugger.jar pode ser encontrado:
- Colocar como diretório de trabalho o "/bin" onde está o SoarDebugger.jar
O funcionamento das regras está descrito a seguir:
Atualização a partir do input-link
A cada ciclo armazena dentro da extensão "^MEMORY", criada dentro do DemoSOAR mas nunca apagada, todas as entidades que receber através de "input-link.CREATURE.SENSOR.VISUAL", nas extensões ^ENTITY.
Para tanto, utiliza 2 operadores: "seeEntityWithMemoryCount" e "seeEntityWithoutMemoryCount".
É a partir das informações armazenadas em "^MEMORY" que as ações do agente serão defindas.
Proposições de ações
Propõe operador "wander" no caso de não haver alterações no sensor visual; esse operador a criatura ficar em rotação para direita com o objetivo dela OLHAR ao seu redor.
Caso existam elementos avistados, pode propor os seguintes operadores:
- "moveFood": propõe o movimento até um alimento cuja posição está armazenada na memória
- "eatFood": propõe que coma um alimento que estiver a menos de 30 de distância
- "moveJewel": propõe o movimento até uma jóia cuja posição está armazenada na memória
- "getJewel": propõe que apanhe uma jóia que estiver a menos de 30 de distância
Prorização dos operadores:
As prioridades dos operadores são as seguintes:
- Prefere atualizar a memória com o que está vendo do que se mover;
- Prefere desviar de um bloco do que atualizar a memória;
- Prefere pegar jóia ou comer alimento do que desviar de bloco;
- Prefere comer alimento do que se mover;
- Entre mover para comida ou jóia, move para comida caso o combustível esteja abaixo ou igual ao limiar estabelecido (400);
- Entre mover para comida ou jóia, move para jóia caso o combustível esteja acima do limiar;
- Move para alimento/jóia que estiver mais perto;
- Pega jóia que estiver mais perto;
- Come alimento que estiver mais perto;
- Desvia do bloco que estiver mais perto;
- Wander é o menos prioritário;