/******************************************************************************
*
* Copyright 2014 Paphus Solutions Inc.
*
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package org.botlibre.thought;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import org.botlibre.Bot;
import org.botlibre.api.knowledge.Memory;
import org.botlibre.api.knowledge.MemoryEventListener;
import org.botlibre.api.knowledge.Vertex;
import org.botlibre.api.thought.Mind;
import org.botlibre.api.thought.Thought;
/**
* Controls and manages the thought processing.
*/
public class BasicMind implements Mind {
public static long UNACTIVE_TO_ASLEEP = 10 * 60 * 1000; // 10 minutes.
public static long UNACTIVE_TO_BORED = 1 * 60 * 1000; // 1 minute.
public static ExecutorService threadPool = Executors.newCachedThreadPool();
/**
* Defines the states of mind.
* The state is influenced by the activity of the senses,
* and affects the depth of conscious thought.
* Only 5 levels for now, but thinking should levels
* should be increments of 100 not 1.
*/
protected MentalState state;
/**
* Keeps track of the time was last active.
*/
protected long lastActiveTime;
protected Bot bot;
/**
* List of thoughts, order represents priority.
*/
protected Map<String, Thought> thoughts;
/**
* List of thoughts, by simple name.
*/
protected Map<String, Thought> thoughtsBySimpleName;
protected Thread consciousThread;
protected volatile boolean consciousRunRequired;
protected Thread subconsciousThread;
protected volatile boolean subconsciousRunRequired;
protected MemoryEventListener listener;
public BasicMind() {
this.thoughts = new LinkedHashMap<String, Thought>();
this.thoughtsBySimpleName = new LinkedHashMap<String, Thought>();
this.state = MentalState.UNCONSCIOUS;
this.lastActiveTime = System.currentTimeMillis();
}
/**
* Return the state of mind.
*/
@Override
public MentalState getState() {
return this.state;
}
/**
* Set the state of mind.
*/
public synchronized void setState(MentalState state) {
log("Changing state", Level.INFO, state.name());
this.state = state;
}
/**
* Ensure the minimum state.
*/
public synchronized void incrementState(MentalState state) {
if ((this.state.ordinal() < state.ordinal()) && (this.state != MentalState.UNCONSCIOUS)) {
setState(state);
}
}
/**
* Ensure the maximum state.
*/
public synchronized void decrementState(MentalState state) {
if (this.state.ordinal() > state.ordinal()) {
setState(state);
}
}
/**
* Return if in an conscious state.
*/
public boolean isConscious() {
return this.state.ordinal() > MentalState.UNCONSCIOUS.ordinal();
}
/**
* Return if in an active state.
*/
@Override
public boolean isActive() {
return this.state.ordinal() >= MentalState.ACTIVE.ordinal();
}
/**
* Return if in an sleep state.
*/
@Override
public boolean isAsleep() {
return this.state.ordinal() >= MentalState.ASLEEP.ordinal();
}
/**
* Return if in an bored state.
*/
@Override
public boolean isBored() {
return this.state.ordinal() >= MentalState.ASLEEP.ordinal();
}
/**
* Log the message if the debug level is greater or equal to the level.
*/
public void log(String message, Level level, Object... arguments) {
this.bot.log(this, message, level, arguments);
}
/**
* Log the exception.
*/
public void log(Throwable exception) {
this.bot.log(this, exception);
}
/**
* Return Bot.
*/
@Override
public Bot getBot() {
return bot;
}
/**
* Set Bot.
*/
@Override
public void setBot(Bot bot) {
this.bot = bot;
}
/**
* Initialize any configurable settings from the properties.
*/
@Override
public void initialize(Map<String, Object> properties) {
return;
}
@Override
public void shutdown() {
this.bot.log(this, "Shutting down", Bot.FINE);
setState(MentalState.UNCONSCIOUS);
// Allow thought threads to complete.
Memory memory = this.bot.memory();
try {
synchronized (memory) {
memory.notifyAll();
}
for (Thought thought : getThoughts().values()) {
if (!thought.isConscious()) {
synchronized (thought) {
thought.notifyAll();
}
}
}
for (int count = 0; count < 10; count++) {
if (this.consciousThread == null) {
break;
}
Thread.sleep(100);
}
for (int count = 0; count < 50; count++) {
if (this.subconsciousThread == null) {
break;
}
Thread.sleep(100);
}
for (Thought thought : getThoughts().values()) {
thought.stop();
}
for (int count = 0; count < 50; count++) {
if (this.subconsciousThread == null) {
break;
}
Thread.sleep(100);
}
this.bot.memory().removeListener(this.listener);
} catch (InterruptedException ignore) {}
this.bot.log(this, "Shutdown complete", Bot.FINE);
}
@Override
public void pool() {
this.bot.log(this, "Pool", Bot.FINE);
for (Thought thought : getThoughts().values()) {
thought.pool();
}
}
/**
* Spawn a thread to run the thoughts.
* Currently only uses a single thread,
* but should probably have a least one thread per thought, or more.
*/
@Override
public void awake() {
this.bot.log(this, "Awake", Bot.FINE);
for (Thought thought : getThoughts().values()) {
try {
thought.awake();
} catch (Exception exception) {
log(exception);
}
}
setState(MentalState.BORED);
this.listener = new MemoryEventListener() {
public void addActiveMemory(Vertex vertex) {
if (!isConscious()) {
return;
}
try {
consciousRunRequired = true;
subconsciousRunRequired = true;
if (consciousThread == null) {
threadPool.execute(new Runnable() {
public void run() {
consciousThread = Thread.currentThread();
try {
while (consciousRunRequired) {
consciousRunRequired = false;
processConsciousThoughts();
}
} finally {
consciousThread = null;
}
}
});
}
if (subconsciousThread == null) {
threadPool.execute(new Runnable() {
public void run() {
subconsciousThread = Thread.currentThread();
try {
while (subconsciousRunRequired) {
subconsciousRunRequired = false;
for (Thought thought : getThoughts().values()) {
if (!thought.isConscious()) {
if (!isConscious()) {
break;
}
if (thought.isStopped()) {
continue;
}
// Don't run when busy.
if (thought.isCritical() || state.ordinal() < MentalState.ALERT.ordinal()) {
thought.think();
}
try {
Thread.sleep(1);
} catch (Exception interupted) {}
}
}
}
} finally {
subconsciousThread = null;
}
}
});
}
} catch (Exception failed) {
bot.log(this, failed);
}
}
};
this.bot.memory().addListener(this.listener);
}
/**
* Process all conscious thoughts, in-order, starting at Consciousness.
* Conscious thoughts use the short-term memory and are single threaded.
*/
public void processConsciousThoughts() {
Memory memory = this.bot.memory();
// Ensure no senses add to the network while processing.
try {
synchronized (memory) {
try {
memory.wait(10);
} catch (InterruptedException exception) {}
if (!isConscious()) {
return;
}
if (!memory.getActiveMemory().isEmpty()) {
incrementState(MentalState.ACTIVE);
// Save reset vertices in memory to allow picking up new relationships.
memory.save();
// Process emotion
this.bot.mood().evaluate();
// Process each conscious thought serially.
// (sub-conscious are processed concurrently), but conscious has a single shared memory.
for (Thought thought : this.thoughts.values()) {
try {
if (thought.isConscious()) {
thought.think();
}
} catch (Exception failed) {
this.bot.log(this, failed);
}
}
// Clear active.
memory.getActiveMemory().clear();
memory.save();
setLastActiveTime(System.currentTimeMillis());
try {
memory.wait(10);
} catch (InterruptedException exception) {}
// If another event has occurred during the processing of the current events, then increase the stress level.
int size = memory.getActiveMemory().size();
if (size > 0) {
incrementState(MentalState.ALERT);
try {
memory.wait(1);
} catch (InterruptedException exception) {}
if (memory.getActiveMemory().size() > size) {
log("Sensory overload", Bot.WARNING, size);
incrementState(MentalState.PANIC);
}
}
}
try {
memory.wait(10);
} catch (InterruptedException exception) {}
// If no event has occurred decrease the stress level.
if (memory.getActiveMemory().isEmpty()) {
int state = this.state.ordinal();
if (state >= MentalState.PANIC.ordinal()) {
decrementState(MentalState.ALERT);
} else if (state >= MentalState.ALERT.ordinal()) {
decrementState(MentalState.ACTIVE);
} else if (state >= MentalState.BORED.ordinal()) {
long unactiveTime = getUnactiveTime();
if (unactiveTime > UNACTIVE_TO_ASLEEP) {
decrementState(MentalState.ASLEEP);
} else if (unactiveTime > UNACTIVE_TO_BORED) {
decrementState(MentalState.BORED);
}
}
}
}
} catch (Exception exception) {
log(exception);
} catch (Throwable exception) {
log(exception);
memory.getActiveMemory().clear();
memory.getShortTermMemory().clear();
memory.getLongTermMemory().clear();
memory.freeMemory();
}
}
@Override
public Map<String, Thought> getThoughts() {
return this.thoughts;
}
@Override
@SuppressWarnings("unchecked")
public <T> T getThought(Class<T> type) {
return (T)this.thoughts.get(type.getName());
}
@Override
public Thought getThought(String name) {
Thought thought = this.thoughts.get(name);
if (thought == null) {
thought = this.thoughtsBySimpleName.get(name);
}
return thought;
}
@Override
public void addThought(Thought thought) {
thought.setBot(this.bot);
this.thoughts.put(thought.getName(), thought);
// Also index simple name.
this.thoughtsBySimpleName.put(thought.getClass().getSimpleName(), thought);
this.thoughtsBySimpleName.put(thought.getClass().getSimpleName().toLowerCase(), thought);
}
@Override
public void removeThought(Thought thought) {
this.thoughts.remove(thought.getName());
// Also index simple name.
this.thoughtsBySimpleName.remove(thought.getClass().getSimpleName());
this.thoughtsBySimpleName.remove(thought.getClass().getSimpleName().toLowerCase());
}
/**
* Print a useful string representation of the mind.
*/
@Override
public String toString() {
return getClass().getSimpleName() + "(" + this.state.name() + ")";
}
/**
* Returns the amount of time since was last active.
*/
public long getUnactiveTime() {
return System.currentTimeMillis() - getLastActiveTime();
}
public long getLastActiveTime() {
return lastActiveTime;
}
public void setLastActiveTime(long lastActiveTime) {
this.lastActiveTime = lastActiveTime;
}
}