Browse Source

added javafx ui in game

master
Rohan Sircar 8 months ago
parent
commit
2d3fea4fd8
  1. 20
      build.sbt
  2. BIN
      lib/jme-jfx-11-1.1.5.jar
  3. 10
      src/main/scala/com/jayfella/jme/jfx/package.scala
  4. 98
      src/main/scala/com/jme3/animation/package.scala
  5. 5
      src/main/scala/com/jme3/app/package.scala
  6. 36
      src/main/scala/com/jme3/input/controls/package.scala
  7. 3
      src/main/scala/com/jme3/input/package.scala
  8. 4
      src/main/scala/com/jme3/scene/package.scala
  9. 3
      src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala
  10. 94
      src/main/scala/wow/doge/mygame/Main.scala
  11. 145
      src/main/scala/wow/doge/mygame/MainApp.scala
  12. 142
      src/main/scala/wow/doge/mygame/game/GameApp.scala
  13. 38
      src/main/scala/wow/doge/mygame/game/GameApp2.scala
  14. 13
      src/main/scala/wow/doge/mygame/game/GameModule.scala
  15. 4
      src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala
  16. 8
      src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala
  17. 4
      src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala
  18. 33
      src/main/scala/wow/doge/mygame/game/subsystems/ai/GdxAiTest.scala
  19. 43
      src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/Graph.java
  20. 371
      src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/IndexedAStarPathFinder.java
  21. 41
      src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/MyIndexedGraph.java
  22. 24
      src/main/scala/wow/doge/mygame/game/subsystems/input/GameInputHandler.scala
  23. 6
      src/main/scala/wow/doge/mygame/game/subsystems/level/DefaultGameLevel.scala
  24. 12
      src/main/scala/wow/doge/mygame/game/subsystems/level/GameLevel.scala
  25. 43
      src/main/scala/wow/doge/mygame/game/subsystems/ui/JmeJfx.scala
  26. 88
      src/main/scala/wow/doge/mygame/implicits/JavaFXMonixObservables.scala
  27. 43
      src/main/scala/wow/doge/mygame/implicits/TestEnum.scala
  28. 18
      src/main/scala/wow/doge/mygame/implicits/observables/package.scala
  29. 69
      src/main/scala/wow/doge/mygame/implicits/package.scala
  30. 102
      src/main/scala/wow/doge/mygame/launcher/DefaultUI.scala
  31. 153
      src/main/scala/wow/doge/mygame/launcher/Launcher.scala
  32. 2
      src/main/scala/wow/doge/mygame/subsystems/events/MovementEvents.scala
  33. 3
      src/main/scala/wow/doge/mygame/subsystems/moddingsystem/ModdingSystem.scala
  34. 47
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptActor.scala
  35. 73
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptCachingActor.scala
  36. 1
      src/main/scala/wow/doge/mygame/subsystems/scriptsystem/ScriptSystemModule.scala
  37. 750
      src/main/scala/wow/doge/mygame/utils/BorderlessScene.scala
  38. 104
      src/main/scala/wow/doge/mygame/utils/GenericConsoleStream.scala
  39. 71
      src/main/scala/wow/doge/mygame/utils/JFXConsoleStream.scala
  40. 130
      src/main/scala/wow/doge/mygame/utils/ResizeHelper.java
  41. 24
      src/main/scala/wow/doge/mygame/utils/TreeTest.scala

20
build.sbt

@ -21,6 +21,7 @@ scalaVersion := "2.13.3"
resolvers += "Jcenter" at "https://jcenter.bintray.com/"
resolvers += "JME Bintray" at "https://bintray.com/jmonkeyengine/com.jme3"
resolvers += "Jitpack" at "https://jitpack.io"
resolvers += Resolver.mavenLocal
resolvers += Resolver.sonatypeRepo("snapshots")
@ -48,7 +49,7 @@ lazy val root = (project in file(".")).settings(
organization := "wow.doge",
version := "1.0-SNAPSHOT",
libraryDependencies ++= Seq(
"org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2",
// "org.scala-lang.modules" %% "scala-parser-combinators" % "1.1.2",
// https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-core
"org.jmonkeyengine" % "jme3-core" % jmeVersion,
// https://mvnrepository.com/artifact/org.jmonkeyengine/jme3-desktop
@ -58,23 +59,18 @@ lazy val root = (project in file(".")).settings(
"org.jmonkeyengine" % "jme3-effects" % jmeVersion,
"org.jmonkeyengine" % "jme3-plugins" % jmeVersion,
"org.jmonkeyengine" % "jme3-blender" % jmeVersion,
// https://mvnrepository.com/artifact/com.github.stephengold/Minie
"com.github.stephengold" % "Minie" % "3.0.0",
// https://mvnrepository.com/artifact/com.simsilica/zay-es
"com.simsilica" % "zay-es" % "1.2.1",
"org.typelevel" %% "cats-core" % "2.1.1",
"com.lihaoyi" % "ammonite" % "2.2.0" cross CrossVersion.full,
"org.jetbrains.kotlin" % "kotlin-main-kts" % "1.4.10",
"org.jetbrains.kotlin" % "kotlin-scripting-jsr223" % "1.4.10",
"org.codehaus.groovy" % "groovy-all" % "3.0.6" pomOnly (),
// "wow.doge" % "game" % "1.0-SNAPSHOT",
"org.scalafx" %% "scalafx" % "14-R19",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.10",
// "ch.qos.logback" % "logback-classic" % "1.2.3",
"org.typelevel" %% "cats-core" % "2.1.1",
"org.typelevel" %% "cats-effect" % "2.1.4",
"io.monix" %% "monix" % "3.2.2",
// "io.monix" %% "monix-bio" % "1.0.0",
"io.monix" %% "monix-bio" % "1.1.0",
"io.circe" %% "circe-core" % "0.13.0",
"io.circe" %% "circe-generic" % "0.13.0",
@ -85,15 +81,21 @@ lazy val root = (project in file(".")).settings(
"com.github.valskalla" %% "odin-monix" % "0.8.1",
"com.github.valskalla" %% "odin-json" % "0.9.1",
"com.softwaremill.macwire" %% "util" % "2.3.7",
"com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided",
"com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
"com.softwaremill.macwire" %% "macros" % "2.3.7" % "provided",
// "com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
"com.github.valskalla" %% "odin-slf4j" % "0.8.1",
"com.softwaremill.quicklens" %% "quicklens" % "1.6.1",
"org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.0-RC1",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
"io.circe" %% "circe-config" % "0.8.0",
"com.beachape" %% "enumeratum-circe" % "1.6.1",
"com.lihaoyi" %% "os-lib" % "0.7.1"
"com.lihaoyi" %% "os-lib" % "0.7.1",
// "com.jayfella" % "jme-jfx-11" % "1.1.5",
"com.github.goxr3plus" % "FX-BorderlessScene" % "4.4.0",
"com.github.Oshan96" % "CustomStage" % "v1.3.1",
"com.badlogicgames.gdx" % "gdx-ai" % "1.8.2",
"org.recast4j" % "recast" % "1.2.5",
"org.recast4j" % "detour" % "1.2.5"
),
// Determine OS version of JavaFX binaries

BIN
lib/jme-jfx-11-1.1.5.jar

10
src/main/scala/com/jayfella/jme/jfx/package.scala

@ -0,0 +1,10 @@
package com.jayfella.jme
package object jfx {
// object JavaFxUI {
// def apply(app: Application) = {
// JavaFxUI.initialize(app)
// JavaFxUI.getInstance()
// }
// }
}

98
src/main/scala/com/jme3/animation/package.scala

@ -4,59 +4,69 @@ import com.jme3.input.Action
package object animation {
implicit class AnimChannelWrap(private val uval: AnimChannel) extends AnyVal {
implicit final class AnimChannelWrap(private val uval: AnimChannel)
extends AnyVal {
/**
* Set the current animation that is played by this AnimChannel.
* <p>
* See {@link #setAnim(java.lang.String, float)}.
* The blendTime argument by default is 150 milliseconds.
*
* @param action The action (name) of the animation to play
*/
def setAnim(action: Action): Unit = uval.setAnim(action.name)
/**
* Set the current animation that is played by this AnimChannel.
* <p>
* This resets the time to zero, and optionally blends the animation
* over <code>blendTime</code> seconds with the currently playing animation.
* Notice that this method will reset the control's speed to 1.0.
*
* @param action The action (name) of the animation to play
* @param blendTime The blend time over which to blend the new animation
* with the old one. If zero, then no blending will occur and the new
* animation will be applied instantly.
*/
def setAnim(action: Action, blendTime: Float): Unit = uval.setAnim(action.name, blendTime)
* Set the current animation that is played by this AnimChannel.
* <p>
* See {@link #setAnim(java.lang.String, float)}.
* The blendTime argument by default is 150 milliseconds.
*
* @param action The action (name) of the animation to play
*/
def setAnim(action: Action): Unit = uval.setAnim(action.name)
/**
* Set the current animation that is played by this AnimChannel.
* <p>
* This resets the time to zero, and optionally blends the animation
* over <code>blendTime</code> seconds with the currently playing animation.
* Notice that this method will reset the control's speed to 1.0.
*
* @param action The action (name) of the animation to play
* @param blendTime The blend time over which to blend the new animation
* with the old one. If zero, then no blending will occur and the new
* animation will be applied instantly.
*/
def setAnim(action: Action, blendTime: Float): Unit =
uval.setAnim(action.name, blendTime)
}
implicit class AnimEventListenerWrap(private val uval: AnimEventListener) extends AnyVal {
implicit final class AnimEventListenerWrap(
private val uval: AnimEventListener
) extends AnyVal {
/**
* Invoked when an animation "cycle" is done. For non-looping animations,
* this event is invoked when the animation is finished playing. For
* looping animations, this even is invoked each time the animation is restarted.
*
* @param control The control to which the listener is assigned.
* @param channel The channel being altered
* @param action The new animation action that is done.
*/
def onAnimCycleDone(control: AnimControl, channel: AnimChannel, action: Action): Unit =
* Invoked when an animation "cycle" is done. For non-looping animations,
* this event is invoked when the animation is finished playing. For
* looping animations, this even is invoked each time the animation is restarted.
*
* @param control The control to which the listener is assigned.
* @param channel The channel being altered
* @param action The new animation action that is done.
*/
def onAnimCycleDone(
control: AnimControl,
channel: AnimChannel,
action: Action
): Unit =
uval.onAnimCycleDone(control, channel, action.name)
/**
* Invoked when a animation is set to play by the user on the given channel.
*
* @param control The control to which the listener is assigned.
* @param channel The channel being altered
* @param action The new animation action set.
*/
def onAnimChange(control: AnimControl, channel: AnimChannel, action: Action): Unit =
uval.onAnimChange(control, channel, action.name)
* Invoked when a animation is set to play by the user on the given channel.
*
* @param control The control to which the listener is assigned.
* @param channel The channel being altered
* @param action The new animation action set.
*/
def onAnimChange(
control: AnimControl,
channel: AnimChannel,
action: Action
): Unit =
uval.onAnimChange(control, channel, action.name)
}
}
}

5
src/main/scala/com/jme3/app/package.scala

@ -5,12 +5,13 @@ package com.jme3
*/
package object app {
implicit class SimpleApplicationWrap(private val uval: SimpleApplication) extends AnyVal {
implicit final class SimpleApplicationWrap(
private val uval: SimpleApplication
) extends AnyVal {
//FIXME: proof of concept, remove later
def testWrap: String = uval.hashCode().toString
}
}

36
src/main/scala/com/jme3/input/controls/package.scala

@ -5,31 +5,33 @@ package com.jme3.input
*/
package object controls {
implicit class ActionListenerWrap(private val uval: ActionListener) extends AnyVal {
implicit final class ActionListenerWrap(private val uval: ActionListener)
extends AnyVal {
/**
* Called when an input to which this listener is registered to is invoked.
*
* @param action The action (name) of the mapping that was invoked
* @param isPressed True if the action is "pressed", false otherwise
* @param tpf The time per frame value.
*/
* Called when an input to which this listener is registered to is invoked.
*
* @param action The action (name) of the mapping that was invoked
* @param isPressed True if the action is "pressed", false otherwise
* @param tpf The time per frame value.
*/
def onAction(action: Action, keyPressed: Boolean, tpf: Float): Unit =
uval.onAction(action.name, keyPressed, tpf)
}
implicit class AnalogListenerWrap(private val uval: AnalogListener) extends AnyVal {
implicit final class AnalogListenerWrap(private val uval: AnalogListener)
extends AnyVal {
/**
* Called to notify the implementation that an analog event has occurred.
*
* The results of KeyTrigger and MouseButtonTrigger events will have tpf
* == value.
*
* @param action The action (name) of the mapping that was invoked
* @param value Value of the axis, from 0 to 1.
* @param tpf The time per frame value.
*/
* Called to notify the implementation that an analog event has occurred.
*
* The results of KeyTrigger and MouseButtonTrigger events will have tpf
* == value.
*
* @param action The action (name) of the mapping that was invoked
* @param value Value of the axis, from 0 to 1.
* @param tpf The time per frame value.
*/
def onAnalog(action: Action, value: Float, tpf: Float): Unit =
uval.onAnalog(action.name, value, tpf)
}

3
src/main/scala/com/jme3/input/package.scala

@ -8,7 +8,8 @@ import com.jme3.input.controls.Trigger
*/
package object input {
implicit class InputManagerWrap(private val uval: InputManager) extends AnyVal {
implicit final class InputManagerWrap(private val uval: InputManager)
extends AnyVal {
def addMapping(action: Action, triggers: Trigger*): Unit =
uval.addMapping(action.name, triggers: _*)
def addListener(listener: InputListener, actions: Action*): Unit =

4
src/main/scala/com/jme3/scene/package.scala

@ -33,14 +33,14 @@ package object scene {
def apply(name: String): Node = new Node(name)
}
implicit class NodeWrap(private val uval: Node) extends AnyVal {
implicit final class NodeWrap(private val uval: Node) extends AnyVal {
def getControlMaybe[T <: Control](controlType: Class[T]): Option[T] =
Option(uval.getControl(controlType))
}
implicit class SpatialWrap(private val uval: Spatial) extends AnyVal {
implicit final class SpatialWrap(private val uval: Spatial) extends AnyVal {
def toNode: Either[ClassCastException, Node] =
uval match {

3
src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala

@ -69,6 +69,9 @@ class StaticLoggerBinder extends OdinLoggerBinder[IO] {
case asyncHttpClient
if asyncHttpClient.startsWith("org.asynchttpclient.netty") =>
defaultConsoleLogger.withMinimalLevel(Level.Warn)
case s if s.startsWith("com.jayfella.jme.jfx.util.JfxPlatform") =>
defaultConsoleLogger.withMinimalLevel(Level.Info)
// case s
// if s.startsWith(
// "wow.doge.mygame.subsystems.movement.PlayerMovementEventHandler"

94
src/main/scala/wow/doge/mygame/Main.scala

@ -2,7 +2,6 @@ package wow.doge.mygame
import scala.concurrent.duration._
import _root_.monix.bio.Task
import akka.util.Timeout
import cats.effect.ExitCode
import cats.implicits._
@ -11,17 +10,19 @@ import io.odin._
import io.odin.json.Formatter
import io.odin.syntax._
import wow.doge.mygame.game.GameAppResource
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
import _root_.monix.bio.BIOApp
import _root_.monix.bio.Task
import _root_.monix.bio.UIO
import cats.effect.Resource
import scalafx.scene.control.TextArea
import wow.doge.mygame.utils.GenericConsoleStream
object Main extends BIOApp with MainModule {
import java.util.logging.{Logger => JLogger, Level}
JLogger.getLogger("").setLevel(Level.SEVERE)
implicit val timeout = Timeout(1.second)
def appResource =
def appResource(consoleStream: GenericConsoleStream[TextArea]) =
for {
logger <-
consoleLogger().withAsync(timeWindow = 1.milliseconds) |+|
@ -31,89 +32,38 @@ object Main extends BIOApp with MainModule {
).withAsync(timeWindow = 1.milliseconds)
jmeScheduler <- jMESchedulerResource
actorSystem <- actorSystemResource2(logger)
scriptCacheActor <- new ScriptSystemResource(os.pwd, actorSystem)(
timeout,
actorSystem.scheduler
).make
// akkaScheduler = actorSystemResource2.scheduler
// consoleTextArea <- Resource.liftF(Task(new TextArea()))
// consoleStream <- wireWith(JFXConsoleStream.textAreaStream _)
(gameApp) <- {
gameApp <- {
// new BulletAppState()
// bas.setThreadingType(Thr)
// gameAppResource(new StatsAppState())
wire[GameAppResource].get2
wire[GameAppResource].get
}
_ <- Resource.liftF(
new MainApp(logger, gameApp, actorSystem, jmeScheduler)(
new MainApp(
logger,
gameApp,
actorSystem,
jmeScheduler,
schedulers,
consoleStream
)(
timeout,
actorSystem.scheduler
).gameInit
).program
)
// fib <- Resource.liftF(
// gameApp
// .enqueueL(() =>
// new MainApp(logger, gameApp, actorSystem, jmeScheduler)(
// timeout,
// actorSystem.scheduler
// )
// )
// .start
// )
// _ <- Resource.liftF(fib.join.flatMap(_.gameInit)
// app = gameApp
// inputManager = gameApp.inputManager
// assetManager = gameApp.assetManager
// bulletAppState = new BulletAppState()
// (playerMovementEventBus, playerCameraEventBus) <- new EventsModule2(
// actorSystem
// ).resource
// b1 = playerMovementEventBus
// b2 = playerCameraEventBus
// playerPos = ImVector3f.ZERO
// playerNode = None.taggedWith[Player]
// modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o".taggedWith[Player]
// playerController <- Resource.liftF {
// implicit val s = actorSystem.scheduler
// wire[PlayerController.Props].create.onErrorHandle(err =>
// logger.error(err.toString())
// )
// }
// gameSystemsInitializerFib <- Resource.make(
// logger.info("creating game systems initializer") >>
// gameApp
// .enqueueL(() => wire[GameSystemsInitializer])
// .start
// )(c => logger.info("destroying game systems initializer") >> c.cancel)
// _ <- Resource.liftF(gameSystemsInitializerFib.join.flatMap(_.init))
} yield ()
// def createPlayerController(
// playerMovementEventBus: ActorRef[
// EventBus.Command[PlayerMovementEvent]
// ],
// playerCameraEventBus: ActorRef[EventBus.Command[PlayerCameraEvent]]
// ): IO[PlayerController.Error, Unit] = {
// val playerPos = ImVector3f.ZERO
// val playerNode = None.taggedWith[Player]
// val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
// wire[PlayerController.Props].create
// }
def run(args: List[String]): UIO[ExitCode] = {
// Console.withOut(
// new JFXConsoleStream(
// new scalafx.scene.control.TextArea(),
// new ByteArrayOutputStream(35)
// )
// )(())
appResource
.use(_ => Task.unit)
.onErrorHandle(_.printStackTrace())
.as(ExitCode.Success)
lazy val consoleStream = GenericConsoleStream.textAreaStream()
Console.withOut(consoleStream)(
appResource(consoleStream)
.use(_ => Task.unit)
.onErrorHandle(_.printStackTrace())
.as(ExitCode.Success)
)
}
}

145
src/main/scala/wow/doge/mygame/MainApp.scala

@ -20,7 +20,7 @@ import monix.bio.IO
import monix.bio.Task
import wow.doge.mygame.events.EventBus
import wow.doge.mygame.game.GameApp2
import wow.doge.mygame.game.nodes.Player
import wow.doge.mygame.game.nodes.PlayerTag
import wow.doge.mygame.game.nodes.PlayerController
import wow.doge.mygame.game.subsystems.input.GameInputHandler
import wow.doge.mygame.implicits._
@ -32,12 +32,32 @@ import wow.doge.mygame.game.subsystems.level.DefaultGameLevel
import com.jme3.renderer.ViewPort
import wow.doge.mygame.subsystems.scriptsystem.ScriptSystemResource
import wow.doge.mygame.subsystems.scriptsystem.ScriptInitMode
import wow.doge.mygame.launcher.Launcher
import wow.doge.mygame.executors.Schedulers
import scalafx.application.JFXApp.PrimaryStage
import scalafx.geometry.Insets
import scalafx.scene.Scene
import scalafx.scene.control.Button
import scalafx.scene.layout.StackPane
import scalafx.scene.paint.Color
import scalafx.scene.shape.Rectangle
import scalafx.Includes._
import scala.concurrent.duration._
import cats.effect.concurrent.Deferred
import monix.bio.Fiber
import wow.doge.mygame.launcher.Launcher.LauncherResult
import scalafx.scene.control.TextArea
import com.jayfella.jme.jfx.JavaFxUI
import wow.doge.mygame.utils.GenericConsoleStream
import java.io.PrintStream
class MainApp(
logger: Logger[Task],
gameApp: GameApp2,
spawnProtocol: ActorSystem[SpawnProtocol.Command],
jmeThread: monix.execution.Scheduler
jmeThread: monix.execution.Scheduler,
schedulers: Schedulers,
consoleStream: GenericConsoleStream[TextArea]
)(implicit
@annotation.unused timeout: Timeout,
@annotation.unused scheduler: Scheduler
@ -46,39 +66,96 @@ class MainApp(
lazy val scriptSystemInit =
new ScriptSystemResource(os.pwd, spawnProtocol, ScriptInitMode.Eager).init
lazy val gameInit: Task[Unit] = for {
eventsModule <- Task(new EventsModule2(spawnProtocol))
playerMovementEventBus <- eventsModule.playerMovementEventBusTask
playerCameraEventBus <- eventsModule.playerCameraEventBusTask
gameAppFib <- gameApp.start.executeOn(jmeThread).start
/**
* schedule a fiber to run on the JME thread and wait for it's completion
* before proceeding forward, as a signal that JME thread has been
* initialized, otherwise we'll get NPEs trying to access the fields
* of the game app
*/
initFib <- gameApp.enqueueL(() => Task("done")).start
_ <- initFib.join
inputManager <- gameApp.inputManager
assetManager <- gameApp.assetManager
stateManager <- gameApp.stateManager
camera <- gameApp.camera
rootNode <- gameApp.rootNode
enqueueR <- Task(gameApp.enqueue _)
viewPort <- gameApp.viewPort
bulletAppState <- Task(new BulletAppState())
appScheduler <- Task(gameApp.scheduler)
// enqueueL <- Task(gameApp.enqueueL _)
_ <- wire[MainAppDelegate].init(gameApp.scheduler)
_ <- gameAppFib.join
} yield ()
def gameInit: Task[Fiber[Throwable, Unit]] =
for {
eventsModule <- Task(new EventsModule2(spawnProtocol))
playerMovementEventBus <- eventsModule.playerMovementEventBusTask
playerCameraEventBus <- eventsModule.playerCameraEventBusTask
gameAppFib <- gameApp.start.executeOn(jmeThread).start
/**
* schedule a task to run on the JME thread and wait for it's completion
* before proceeding forward, as a signal that JME thread has been
* initialized, otherwise we'll get NPEs trying to access the fields
* of the game app
*/
res <- gameApp.enqueueL(() => Task("done")).flatten
_ <- logger.info(s"Result = $res")
inputManager <- gameApp.inputManager
assetManager <- gameApp.assetManager
stateManager <- gameApp.stateManager
camera <- gameApp.camera
rootNode <- gameApp.rootNode
enqueueR <- Task(gameApp.enqueue _)
viewPort <- gameApp.viewPort
_ <- logger.info("before")
// jfxUI <- Task(JavaFxUI.initialize(gameApp.app))
// .executeOn(gameApp.scheduler) >> Task.sleep(500.millis) >> Task(
// JavaFxUI.getInstance()
// )
// .start
jfxUI <- gameApp.jfxUI
consoleTextArea <- Task(new TextArea {
text = "hello"
editable = false
wrapText = true
// maxHeight = 150
// maxWidth = 300
})
_ <- Task(consoleStream := consoleTextArea)
_ <- Task(jfxUI += consoleTextArea)
// consoleStream <- Task(
// GenericConsoleStream.textAreaStream(consoleTextArea)
// )
_ <- logger.info("after")
bulletAppState <- Task(new BulletAppState())
_ <- Task(stateManager.attach(bulletAppState))
_ <- logger.info("Initializing console stream")
// _ <- Task(GenericConsoleStream.textAreaStream(consoleTextArea)).bracket(
// consoleStream =>
// Task { System.setOut(consoleStream) } >> wire[MainAppDelegate]
// .init(gameApp.scheduler, consoleStream)
// // consoleLogger
// // Console.withOut(consoleStream)(
// // wire[MainAppDelegate].init(gameApp.scheduler, consoleStream)
// // )
// )(stream => Task(stream.close()).onErrorHandle(_.printStackTrace()))
// _ <- Task { System.setOut(new PrintStream(consoleStream, true)) }
// _ <- Task {
// Console.withOut(consoleStream)(println("hello"))
// }
_ <- wire[MainAppDelegate].init(gameApp.scheduler)
} yield (gameAppFib)
lazy val program = for {
scriptSystem <- scriptSystemInit
game <- gameInit
/**
* Signal for synchronization between the JavaFX launcher and the in-game JavaFX GUI
* Without this, we get a "Toolkit already initialized" excResult. The launch button
* in the launcher completes the signal. The game init process which listens for this
* signal can then continue
*/
launchSignal <- Deferred[Task, Launcher.LauncherResult]
launcher <- new Launcher.Props(schedulers, launchSignal).create
cancelToken <- launcher.init()
launchResult <- launchSignal.get
_ <- cancelToken.cancel
_ <-
if (launchResult == LauncherResult.Exit)
logger.info("Exiting")
else gameInit.flatMap(_.join)
// _ <- Task.sleep(2000.millis)
// gameAppFib <- gameInit
/**
* Wait for game window to close
*/
// _ <- gameAppFib.join
} yield ()
}
/**
* Class with all dependencies in one place for easy wiring
*/
class MainAppDelegate(
gameApp: GameApp2,
spawnProtocol: ActorSystem[SpawnProtocol.Command],
@ -101,16 +178,20 @@ class MainAppDelegate(
@annotation.unused scheduler: Scheduler
) {
def init(appScheduler: monix.execution.Scheduler) =
def init(
appScheduler: monix.execution.Scheduler
// consoleStream: GenericConsoleStream[TextArea]
) =
for {
_ <- loggerL.info("Initializing Systems")
_ <- Task(stateManager.attach(bulletAppState))
_ <- Task(
assetManager.registerLocator(
(os.rel / "assets" / "town.zip"),
classOf[ZipLocator]
)
)
_ <- loggerL.info("test hmm")
// _ <- Task(consoleStream.println("text"))
_ <- DefaultGameLevel(assetManager, viewPort)
.addToGame(
rootNode,
@ -131,7 +212,7 @@ class MainAppDelegate(
@annotation.unused
val playerPos = ImVector3f.ZERO
@annotation.unused
val playerNode = None.taggedWith[Player]
val playerNode = None.taggedWith[PlayerTag]
@annotation.unused
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
wire[PlayerController.Props].create

142
src/main/scala/wow/doge/mygame/game/GameApp.scala

@ -16,81 +16,26 @@ import wow.doge.mygame.executors.GUIExecutorService
import wow.doge.mygame.executors.Schedulers
class GameApp(
// actorSystem: ActorSystem[SpawnProtocol.Command],
schedulers: Schedulers,
appStates: AppState*
) extends SimpleApplication(appStates: _*) {
import GameApp._
// implicit val timeout = Timeout(10.seconds)
// implicit val scheduler = actorSystem.scheduler
// private lazy val taskQueueS = new ConcurrentLinkedQueue[MyTask[_]]()
/**
* A non blocking synchronized queue using an immutable scala queue and monix's atomic class
*/
private lazy val taskQueue2 = Atomic(Queue.empty[MyTask[_]])
private val tickSubject =
ConcurrentSubject[Float](multicast = MulticastStrategy.publish)(
schedulers.async
)
// (scheduler)
override def simpleInitApp(): Unit = {
println("gameapp" + Thread.currentThread().getName())
// val ship = ed.createEntity()
// val mbState = stateManager().state[EntityDataState]().map(_.getEntityData())
// val mbState = Try(
// stateManager()
// .state[TestAppState]()
// .entity
// ).toOption.flatten.toRight(println("empty"))
// // .flatMap(_.entity)
// val x = mbState.flatMap(
// _.query
// .filter[TestComponent]("name", new Object())
// // .filterOr[TestEntity](
// // Filters
// // .fieldEquals(classOf[TestEntity], "", null)
// // )
// .component[Tag]()
// .component[TestComponent]()
// .result
// .toRight(println("failed"))
// )
// rootNode
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// Future(println("hello"))(jmeEC(this))
// val wbActor: Future[ActorRef[Greeter.Greet]] = actorSystem.ask(
// SpawnProtocol.Spawn(
// behavior = Greeter(),
// name = "listener",
// DispatcherSelector.fromConfig("jme-dispatcher"),
// _
// )
// )
// wbActor.map(a => a.ask(Greeter.Greet("hello", _)).map(println))
}
def tickObservable: Observable[Float] = tickSubject
override def simpleUpdate(tpf: Float): Unit = {
// val rot2 = rot.fromAngleAxis(FastMath.PI, new Vector3f(0, 0, 1))
// val rotation = geom.getLocalRotation()
// rotation.add(rot2)
// geom.rotate(rot2)
override def simpleInitApp(): Unit = {}
// geom.updateModelBound()
// geom.updateGeometricState()
override def simpleUpdate(tpf: Float): Unit = {
tickSubject.onNext(tpf)
}
@ -101,32 +46,14 @@ class GameApp(
def enqueueScala[T](cb: () => T): CancelableFuture[T] = {
val p = Promise[T]()
// p.success(cb())
// taskQueueS.add(MyTask(p, cb))
taskQueue2.transform(_ :+ MyTask(p, cb))
p.future
}
// taskQueue2.transform(_ :+ MyTask(p, cb))
// p
def enqueueL[T](cb: () => T): Task[T] =
// Task(Promise[T]()).flatMap { p =>
// Task(taskQueue2.transform(_ :+ MyTask(p, cb))) >>
// Task.fromCancelablePromise(p)
// }
// Task.fromCancelablePromise {
// val p = Promise[T]()
// taskQueue2.transform(_ :+ MyTask(p, cb))
// p
// }
Task.deferFuture(enqueueScala(cb))
// taskQueueS.add(MyTask(p, cb))
override protected def runQueuedTasks(): Unit = {
// Option(taskQueueS.poll()).foreach {
// case MyTask(p, cb) =>
// p.success(cb())
// }
taskQueue2.transform { current =>
current.dequeueOption.fold(current) {
case (MyTask(p, cb), updated) =>
@ -139,8 +66,9 @@ class GameApp(
}
object JMEExecutorService extends GUIExecutorService {
override def execute(command: Runnable) =
enqueue(command)
override def execute(command: Runnable): Unit =
enqueueScala(() => command.run())
// enqueue(command)
// new SingleThreadEventExecutor()
// sys.addShutdownHook(JMEExecutorService.shutdown())
}
@ -150,3 +78,55 @@ class GameApp(
object GameApp {
private[game] case class MyTask[T](p: Promise[T], cb: () => T)
}
// val ship = ed.createEntity()
// val mbState = stateManager().state[EntityDataState]().map(_.getEntityData())
// val mbState = Try(
// stateManager()
// .state[TestAppState]()
// .entity
// ).toOption.flatten.toRight(println("empty"))
// // .flatMap(_.entity)
// val x = mbState.flatMap(
// _.query
// .filter[TestComponent]("name", new Object())
// // .filterOr[TestEntity](
// // Filters
// // .fieldEquals(classOf[TestEntity], "", null)
// // )
// .component[Tag]()
// .component[TestComponent]()
// .result
// .toRight(println("failed"))
// )
// rootNode
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// .child(geom)
// Future(println("hello"))(jmeEC(this))
// val wbActor: Future[ActorRef[Greeter.Greet]] = actorSystem.ask(
// SpawnProtocol.Spawn(
// behavior = Greeter(),
// name = "listener",
// DispatcherSelector.fromConfig("jme-dispatcher"),
// _
// )
// )
// wbActor.map(a => a.ask(Greeter.Greet("hello", _)).map(println))
// Task(Promise[T]()).flatMap { p =>
// Task(taskQueue2.transform(_ :+ MyTask(p, cb))) >>
// Task.fromCancelablePromise(p)
// }
// Task.fromCancelablePromise {
// val p = Promise[T]()
// taskQueue2.transform(_ :+ MyTask(p, cb))
// p
// }

38
src/main/scala/wow/doge/mygame/game/GameApp2.scala

@ -6,11 +6,16 @@ import com.jme3.asset.AssetManager
import com.jme3.input.InputManager
import monix.bio.IO
import monix.bio.Task
import com.jme3.scene.Node
import monix.catnap.Semaphore
import com.jme3.scene.Spatial
import wow.doge.mygame.game.GameApp2.SynchedObject
import wow.doge.mygame.game.subsystems.ui.JFxUI
sealed trait Error
case object FlyCamNotExists extends Error
class GameApp2(app: GameApp) {
class GameApp2(val app: GameApp) {
def stateManager: Task[AppStateManager] = Task(app.getStateManager())
def inputManager: Task[InputManager] = Task(app.getInputManager())
def assetManager: Task[AssetManager] = Task(app.getAssetManager())
@ -22,6 +27,7 @@ class GameApp2(app: GameApp) {
def camera = Task(app.getCamera())
def viewPort = Task(app.getViewPort())
def rootNode = Ref[Task].of(app.getRootNode())
def rootNode2 = SynchedObject(app.getRootNode())
def enqueue(cb: () => Unit) =
app.enqueue(new Runnable {
override def run() = cb()
@ -31,5 +37,35 @@ class GameApp2(app: GameApp) {
def start = Task(app.start())
def stop = Task(app.stop())
def scheduler = app.scheduler
def jfxUI = JFxUI(app)
}
object GameApp2 {
class WrappedNode(node: Node, lock: Semaphore[Task]) {
def +=(spat: Spatial) = lock.withPermit(Task(node.attachChild(spat)))
}
/**
* Synchronization wrapper for a mutable object
*
* @param obj the mutable object
* @param lock lock for synchronization
*/
class SynchedObject[A](obj: A, lock: Semaphore[Task]) {
def modify(f: A => Unit): Task[Unit] =
lock.withPermit(Task(f(obj)))
def flatModify(f: A => Task[Unit]): Task[Unit] =
lock.withPermit(f(obj))
def get: Task[A] = lock.withPermit(Task(obj))
}
object SynchedObject {
def apply[A](obj: A) =
Semaphore[Task](1).flatMap(lock => Task(new SynchedObject(obj, lock)))
}
}

13
src/main/scala/wow/doge/mygame/game/GameModule.scala

@ -17,7 +17,7 @@ class GameAppResource(
jmeScheduler: Scheduler,
schedulers: Schedulers
) {
def get: Resource[Task, (GameApp2, Fiber[Throwable, Unit])] =
def get2: Resource[Task, (GameApp2, Fiber[Throwable, Unit])] =
Resource.make(
for {
_ <- logger.info("Creating game app")
@ -37,7 +37,7 @@ class GameAppResource(
} yield (app2 -> fib)
)(logger.info("Closing game app") >> _._2.cancel)
def get2: Resource[Task, GameApp2] =
def get: Resource[Task, GameApp2] =
Resource.make(
for {
_ <- logger.info("Creating game app")
@ -45,9 +45,12 @@ class GameAppResource(
app2 <- Task {
val settings = new AppSettings(true)
settings.setVSync(true)
settings.setUseInput(true)
// new FlyCamAppState
// settings.setFrameRate(250)
/**
* disables the launcher
* We'll be making our own launcher anyway
*/
app.setShowSettings(false)
app.setSettings(settings)
// JMERunner.runner = app
new GameApp2(app)

4
src/main/scala/wow/doge/mygame/game/GameSystemsInitializer.scala

@ -22,7 +22,7 @@ import monix.bio.IO
import monix.bio.Task
import monix.reactive.Consumer
import wow.doge.mygame.events.EventBus
import wow.doge.mygame.game.nodes.Player
import wow.doge.mygame.game.nodes.PlayerTag
import wow.doge.mygame.game.nodes.PlayerController
import wow.doge.mygame.game.subsystems.input.GameInputHandler
import wow.doge.mygame.implicits._
@ -86,7 +86,7 @@ class GameSystemsInitializer(
@annotation.unused
val playerPos = ImVector3f.ZERO
@annotation.unused
val playerNode = None.taggedWith[Player]
val playerNode = None.taggedWith[PlayerTag]
@annotation.unused
val modelPath = os.rel / "Models" / "Jaime" / "Jaime.j3o"
wire[PlayerController.Props].create

8
src/main/scala/wow/doge/mygame/game/nodes/PlayerController.scala

@ -32,7 +32,7 @@ import wow.doge.mygame.subsystems.movement.ImMovementActor
import wow.doge.mygame.utils.AkkaUtils
// class PlayerNode(val name: String) extends Node(name) {}
sealed trait Player
sealed trait PlayerTag
sealed trait PlayerCameraNode
object PlayerController {
@ -54,7 +54,7 @@ object PlayerController {
],
playerCameraEventBus: ActorRef[EventBus.Command[PlayerCameraEvent]],
_playerPhysicsControl: Option[BetterCharacterControl],
_playerNode: Option[Node with Player] = None,
_playerNode: Option[Node with PlayerTag] = None,
_cameraNode: Option[CameraNode with PlayerCameraNode] = None,
appScheduler: monix.execution.Scheduler
)(implicit timeout: Timeout, scheduler: Scheduler) {
@ -68,7 +68,7 @@ object PlayerController {
playerPhysicsControl <- IO(
_playerPhysicsControl
.getOrElse(defaultPlayerPhysicsControl)
.taggedWith[Player]
.taggedWith[PlayerTag]
)
playerNode <- IO.fromEither(
_playerNode.fold(
@ -179,7 +179,7 @@ object Methods {
def spawnMovementActor(
enqueueR: Function1[() => Unit, Unit],
spawnProtocol: ActorRef[SpawnProtocol.Command],
movable: BetterCharacterControl @@ Player,
movable: BetterCharacterControl @@ PlayerTag,
playerMovementEventBus: ActorRef[
EventBus.Command[PlayerMovementEvent]
],

4
src/main/scala/wow/doge/mygame/game/nodes/PlayerEventListeners.scala

@ -22,7 +22,7 @@ object PlayerMovementEventListener {
Logger[PlayerMovementEventListener.type].underlying
),
Behaviors.setup[PlayerMovementEvent](ctx =>
Behaviors.receiveMessage {
Behaviors.receiveMessagePartial {
case PlayerMovedLeft(pressed) =>
movementActor ! ImMovementActor.MovedLeft(pressed)
Behaviors.same
@ -69,7 +69,7 @@ object PlayerCameraEventListener {
Logger[PlayerCameraEventListener.type].underlying
),
Behaviors.setup[PlayerCameraEvent](ctx =>
Behaviors.receiveMessage {
Behaviors.receiveMessagePartial {
case CameraMovedUp =>
enqueueR(() => {

33
src/main/scala/wow/doge/mygame/game/subsystems/ai/GdxAiTest.scala

@ -0,0 +1,33 @@
package wow.doge.mygame.game.subsystems.ai
import com.badlogic.gdx.ai.pfa.Connection
import wow.doge.mygame.game.subsystems.ai.gdx.MyIndexedGraph
import scala.collection.immutable.ArraySeq
// import com.badlogic.gdx.ai.pfa.indexed.IndexedGraph
// import scala.jdk.javaapi.CollectionConverters._
case class City(x: Float, y: Float, name: String, index: Int)
case class Street(fromNode: City, toNode: City, cost: Float)
extends Connection[City] {
override def getCost(): Float = cost
override def getFromNode(): City = fromNode
override def getToNode(): City = toNode
}
class CityGraph extends MyIndexedGraph[City] {
override def getConnections(
city: City
): IndexedSeq[Connection[City]] =
ArraySeq(Street(City(0f, 0f, "egw", 0), City(0f, 0f, "egw", 0), 1))
// or Vector(Street(City(0f, 0f, "egw", 0), City(0f, 0f, "egw", 0), 1))
override def getIndex(city: City): Int = ???
override def getNodeCount(): Int = ???
}

43
src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/Graph.java

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright 2014 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 wow.doge.mygame.game.subsystems.ai.gdx;
import com.badlogic.gdx.ai.pfa.Connection;
import com.badlogic.gdx.utils.Array;
// import java.lang.Iterable;
import java.util.List;
import scala.collection.immutable.IndexedSeq;
/**
* A graph is a collection of nodes, each one having a collection of outgoing
* {@link Connection connections}.
*
* @param <N> Type of node
*
* @author davebaol
*/
public interface Graph<N> {
/**
* Returns the connections outgoing from the given node.
*
* @param fromNode the node whose outgoing connections will be returned
* @return the array of connections outgoing from the given node.
*/
public IndexedSeq<Connection<N>> getConnections(N fromNode);
}

371
src/main/scala/wow/doge/mygame/game/subsystems/ai/gdx/IndexedAStarPathFinder.java

@ -0,0 +1,371 @@
/*******************************************************************************
* Copyright 2014 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
*
* 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 wow.doge.mygame.game.subsystems.ai.gdx;
import com.badlogic.gdx.ai.pfa.Connection;
import com.badlogic.gdx.ai.pfa.GraphPath;
import com.badlogic.gdx.ai.pfa.Heuristic;
import com.badlogic.gdx.ai.pfa.PathFinder;
import com.badlogic.gdx.ai.pfa.PathFinderQueue;
import com.badlogic.gdx.ai.pfa.PathFinderRequest;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.BinaryHeap;
import com.badlogic.gdx.utils.TimeUtils;
import java.util.List;
import wow.doge.mygame.game.subsystems.ai.gdx.Graph;
import wow.doge.mygame.game.subsystems.ai.gdx.MyIndexedGraph;
import scala.collection.immutable.IndexedSeq;
/**
* A fully implemented {@link PathFinder} that can perform both interruptible
* and non-interruptible pathfinding.
* <p>
* This implementation is a common variation of the A* algorithm that is faster
* than the general A*.
* <p>
* In the general A* implementation, data are held for each node in the open or
* closed lists, and these data are held as a NodeRecord instance. Records are
* created when a node is first considered and then moved between the open and
* closed lists, as required. There is a key step in the algorithm where the
* lists are searched for a node record corresponding to a particular node. This
* operation is something time-consuming.
* <p>
* The indexed A* algorithm improves execution speed by using an array of all
* the node records for every node in the graph. Nodes must be numbered using
* sequential integers (see {@link MyIndexedGraph#getIndex(Object)}), so we
* don't need to search for a node in the two lists at all. We can simply use
* the node index to look up its record in the array (creating it if it is
* missing). This means that the close list is no longer needed. To know whether
* a node is open or closed, we use the {@link NodeRecord#category category} of
* the node record. This makes the search step very fast indeed (in fact, there
* is no search, and we can go straight to the information we need).
* Unfortunately, we can't get rid of the open list because we still need to be
* able to retrieve the element with the lowest cost. However, we use a
* {@link BinaryHeap} for the open list in order to keep performance as high as
* possible.
*
* @param <N> Type of node
*
* @author davebaol
*/
public class IndexedAStarPathFinder<N> implements PathFinder<N> {
MyIndexedGraph<N> graph;
NodeRecord<N>[] nodeRecords;
BinaryHeap<NodeRecord<N>> openList;
NodeRecord<N> current;
public Metrics metrics;
/** The unique ID for each search run. Used to mark nodes. */
private int searchId;
private static final int UNVISITED = 0;
private static final int OPEN = 1;
private static final int CLOSED = 2;
public IndexedAStarPathFinder(MyIndexedGraph<N> graph) {
this(graph, false);
}
@SuppressWarnings("unchecked")
public IndexedAStarPathFinder(MyIndexedGraph<N> graph, boolean calculateMetrics) {
this.graph = graph;
this.nodeRecords = (NodeRecord<N>[]) new NodeRecord[graph.getNodeCount()];
this.openList = new BinaryHeap<NodeRecord<N>>();
if (calculateMetrics)
this.metrics = new Metrics();
}
@Override
public boolean searchConnectionPath(N startNode, N endNode, Heuristic<N> heuristic,
GraphPath<Connection<N>> outPath) {
// Perform AStar
boolean found = search(startNode, endNode, heuristic);
if (found) {
// Create a path made of connections
generateConnectionPath(startNode, outPath);
}
return found;
}
@Override
public boolean searchNodePath(N startNode, N endNode, Heuristic<N> heuristic, GraphPath<N> outPath) {
// Perform AStar
boolean found = search(startNode, endNode, heuristic);
if (found) {
// Create a path made of nodes
generateNodePath(startNode, outPath);
}
return found;
}
protected boolean search(N startNode, N endNode, Heuristic<N> heuristic) {
initSearch(startNode, endNode, heuristic);
// Iterate through processing each node
do {
// Retrieve the node with smallest estimated total cost from the open list
current = openList.pop();
current.category = CLOSED;
// Terminate if we reached the goal node
if (current.node == endNode)
return true;
visitChildren(endNode, heuristic);
} while (openList.size > 0);
// We've run out of nodes without finding the goal, so there's no solution
return false;
}
@Override
public boolean search(PathFinderRequest<N> request, long timeToRun) {
long lastTime = TimeUtils.nanoTime();
// We have to initialize the search if the status has just changed
if (request.statusChanged) {
initSearch(request.startNode, request.endNode, request.heuristic);
request.statusChanged = false;
}
// Iterate through processing each node
do {
// Check the available time
long currentTime = TimeUtils.nanoTime();
timeToRun -= currentTime - lastTime;
if (timeToRun <= PathFinderQueue.TIME_TOLERANCE)
return false;
// Retrieve the node with smallest estimated total cost from the open list
current = openList.pop();
current.category = CLOSED;
// Terminate if we reached the goal node; we've found a path.
if (current.node == request.endNode) {
request.pathFound = true;
generateNodePath(request.startNode, request.resultPath);
return true;
}
// Visit current node's children
visitChildren(request.endNode, request.heuristic);
// Store the current time
lastTime = currentTime;
} while (openList.size > 0);
// The open list is empty and we've not found a path.
request.pathFound = false;
return true;
}
protected void initSearch(N startNode, N endNode, Heuristic<N> heuristic) {
if (metrics != null)
metrics.reset();
// Increment the search id
if (++searchId < 0)
searchId = 1;
// Initialize the open list
openList.clear();
// Initialize the record for the start node and add it to the open list
NodeRecord<N> startRecord = getNodeRecord(startNode);
startRecord.node = startNode;
startRecord.connection = null;
startRecord.costSoFar = 0;
addToOpenList(startRecord, heuristic.estimate(startNode, endNode));
current = null;
}
protected void visitChildren(N endNode, Heuristic<N> heuristic) {
// Get current node's outgoing connections
IndexedSeq<Connection<N>> connections = graph.getConnections(current.node);
// Loop through each connection in turn
for (int i = 0; i < connections.size(); i++) {
if (metrics != null)
metrics.visitedNodes++;
Connection<N> connection = connections.apply(i);
// Get the cost estimate for the node
N node = connection.getToNode();
float nodeCost = current.costSoFar + connection.getCost();
float nodeHeuristic;
NodeRecord<N> nodeRecord = getNodeRecord(node);
if (nodeRecord.category == CLOSED) { // The node is closed
// If we didn't find a shorter route, skip
if (nodeRecord.costSoFar <= nodeCost)
continue;
// We can use the node's old cost values to calculate its heuristic
// without calling the possibly expensive heuristic function
nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar;
} else if (nodeRecord.category == OPEN) { // The node is open
// If our route is no better, then skip
if (nodeRecord.costSoFar <= nodeCost)
continue;
// Remove it from the open list (it will be re-added with the new cost)
openList.remove(nodeRecord);
// We can use the node's old cost values to calculate its heuristic
// without calling the possibly expensive heuristic function
nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar;
} else { // the node is unvisited
// We'll need to calculate the heuristic value using the function,
// since we don't have a node record with a previously calculated value
nodeHeuristic = heuristic.estimate(node, endNode);
}
// Update node record's cost and connection
nodeRecord.costSoFar = nodeCost;
nodeRecord.connection = connection;
// Add it to the open list with the estimated total cost
addToOpenList(nodeRecord, nodeCost + nodeHeuristic);
}
}
protected void generateConnectionPath(N startNode, GraphPath<Connection<N>> outPath) {
// Work back along the path, accumulating connections
// outPath.clear();
while (current.node != startNode) {
outPath.add(current.connection);
current = nodeRecords[graph.getIndex(current.connection.getFromNode())];
}
// Reverse the path
outPath.reverse();
}
protected void generateNodePath(N startNode, GraphPath<N> outPath) {
// Work back along the path, accumulating nodes
// outPath.clear();
while (current.connection != null) {
outPath.add(current.node);
current = nodeRecords[graph.getIndex(current.connection.getFromNode())];
}
outPath.add(startNode);
// Reverse the path
outPath.reverse();
}
protected void addToOpenList(NodeRecord<N> nodeRecord, float estimatedTotalCost) {
openList.add(nodeRecord, estimatedTotalCost);
nodeRecord.category = OPEN;
if (metrics != null) {
metrics.openListAdditions++;
metrics.openListPeak = Math.max(metrics.openListPeak, openList.size);
}
}
protected NodeRecord<N> getNodeRecord(N node) {
int index = graph.getIndex(node);
NodeRecord<N> nr = nodeRecords[index];
if (nr != null) {
if (nr.searchId != searchId) {
nr.category = UNVISITED;
nr.searchId = searchId;
}
return nr;