Testing out JmonkeyEngine to make a game in Scala with Akka Actors within a pure FP layer
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

230 lines
6.8 KiB

4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
3 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
  1. package wow.doge.mygame.subsystems.moddingsystem
  2. import java.io.FileNotFoundException
  3. import java.nio.file.NoSuchFileException
  4. import scala.collection.View
  5. import scala.collection.immutable.ArraySeq
  6. import cats.Show
  7. import cats.implicits._
  8. import cats.kernel.Eq
  9. import io.circe._
  10. import io.circe.generic.JsonCodec
  11. import io.circe.generic.semiauto._
  12. import io.circe.parser._
  13. import monix.bio.IO
  14. import monix.bio.UIO
  15. import monix.reactive.Consumer
  16. import monix.reactive.Observable
  17. import wow.doge.mygame.utils.IOUtils
  18. import IOUtils.toIO
  19. @JsonCodec
  20. final case class Test1(hello1: String, hello2: String)
  21. @JsonCodec
  22. final case class Test2(hello1: String)
  23. final case class Plugin(name: String, priority: Int)
  24. object Plugin {
  25. implicit val decoder: Decoder[Plugin] = deriveDecoder
  26. implicit val show = Show.fromToString[Plugin]
  27. implicit val eq = Eq.fromUniversalEquals[Plugin]
  28. }
  29. object ModdingSystem {
  30. sealed trait Error
  31. final case class CouldNotDecode(cause: String) extends Error
  32. final case class ParseFailure(cause: io.circe.ParsingFailure) extends Error
  33. final case class DecodingFailure(cause: io.circe.DecodingFailure)
  34. extends Error
  35. final case class FileNotFound(path: os.Path) extends Error
  36. object Error {
  37. implicit val show = Show.fromToString[Error]
  38. implicit val eq = Eq.fromUniversalEquals[Error]
  39. }
  40. def readPluginsList(dir: os.Path): IO[Error, ArraySeq[Plugin]] =
  41. IO(parse(os.read(dir / "plugins.json")))
  42. .onErrorHandleWith {
  43. case _: FileNotFoundException =>
  44. IO.raiseError(FileNotFound(dir / "plugins.json"))
  45. case _: NoSuchFileException =>
  46. IO.raiseError(FileNotFound(dir / "plugins.json"))
  47. }
  48. .flatMap(files =>
  49. IO.fromEither(files)
  50. .map(_.as[ArraySeq[Plugin]])
  51. .mapError(ParseFailure)
  52. )
  53. .flatMap(result => IO.fromEither(result).mapError(DecodingFailure))
  54. def findPluginFiles(dir: os.Path): View[os.Path] =
  55. os.list(dir)
  56. .view
  57. .filter(f => f.ext == "json" && f.baseName.endsWith("plugin"))
  58. def findAndReadPluginFiles(
  59. dir: os.Path,
  60. plugins: ArraySeq[Plugin]
  61. ): UIO[(View[(Plugin, Error)], View[(Plugin, String)])] =
  62. UIO(
  63. plugins
  64. .sortBy(_.priority)
  65. .view
  66. .map { p =>
  67. val path = dir / os.RelPath(p.name + ".plugin.json")
  68. p ->
  69. Either
  70. .catchNonFatal(os.read(path))
  71. .leftMap {
  72. case _: FileNotFoundException => FileNotFound(path)
  73. case _: NoSuchFileException => FileNotFound(path)
  74. }
  75. }
  76. .partitionMap {
  77. case (p, either) =>
  78. either match {
  79. case Left(value) => Left(p -> value)
  80. case Right(value) => Right(p -> value)
  81. }
  82. }
  83. )
  84. // : (View[(Plugin, Error)], View[(Plugin, String)])
  85. def findAndReadPluginFiles2(
  86. dir: os.Path,
  87. plugins: ArraySeq[Plugin]
  88. ) =
  89. // IO.parTraverse(plugins.sortBy(_.priority))(p =>
  90. // IO {
  91. // val path = dir / os.RelPath(p.name + ".plugin.json")
  92. // os.read(path)
  93. // }
  94. // .onErrorHandleWith {
  95. // case _: FileNotFoundException =>
  96. // IO.raiseError(FileNotFound(dir.toString))
  97. // case _: NoSuchFileException =>
  98. // IO.raiseError(FileNotFound(dir.toString))
  99. // }
  100. // .flatMap(r => UIO(p -> r))
  101. // ).map {
  102. // _.partitionMap {
  103. // case (p, either) =>
  104. // either match {
  105. // case Left(value) => Left(p -> value)
  106. // case Right(value) => Right(p -> value)
  107. // }
  108. // }
  109. // }
  110. plugins
  111. .sortBy(_.priority)
  112. .view
  113. .map { p =>
  114. val path = dir / os.RelPath(p.name + ".plugin.json")
  115. p -> IO(os.read(path))
  116. .onErrorHandleWith {
  117. case _: FileNotFoundException => IO.raiseError(FileNotFound(path))
  118. case _: NoSuchFileException => IO.raiseError(FileNotFound(path))
  119. }
  120. // .map(r => p -> r)
  121. }
  122. .map {
  123. case (p, io) =>
  124. io.attempt.map {
  125. case Left(value) => Left(p -> value)
  126. case Right(value) => Right(p -> value)
  127. }
  128. }
  129. .to(List)
  130. .parSequence
  131. // .partitionMap {
  132. // _.map {
  133. // case l @ Left(value) => l
  134. // case r @ Right(value) => r
  135. // }
  136. // }
  137. // .sequence
  138. // def readPluginFiles(filePaths: View[os.Path]) =
  139. // filePaths.map(path => os.read(path))
  140. // def readPluginFiles2(filePaths: View[os.Path]) =
  141. // filePaths
  142. // .map(path =>
  143. // IO(os.read(path)).onErrorHandleWith {
  144. // case _: FileNotFoundException =>
  145. // IO.raiseError(FileNotFound(path))
  146. // case _: NoSuchFileException =>
  147. // IO.raiseError(FileNotFound(path))
  148. // }
  149. // )
  150. // .to(List)
  151. // .parSequence
  152. def parsePluginFiles(files: View[(Plugin, String)]) =
  153. files
  154. .map {
  155. case (p, s) => p -> parse(s)
  156. }
  157. .partitionMap {
  158. case (p, Left(value)) => Left(p -> value)
  159. case (p, Right(value)) => Right(p -> value)
  160. }
  161. val emptyJson = Json.fromString("empty")
  162. val foldFn: (Json, Json) => Json = {
  163. case (json, Json.Null) => json //ignore null values
  164. case (json, value) => json.deepMerge(value)
  165. }
  166. def mergePluginDataConsumer =
  167. Consumer.foldLeft[Json, Json](emptyJson)(foldFn)
  168. def loadBalancedPluginDataMerger =
  169. Consumer
  170. .loadBalance(parallelism = 2, mergePluginDataConsumer)
  171. .map(_.foldLeft(emptyJson)(foldFn))
  172. def run(wd: os.Path = os.pwd) =
  173. for {
  174. plugins <- readPluginsList(wd)
  175. (readFailures, readSuccesses) <- findAndReadPluginFiles(wd, plugins)
  176. (parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses))
  177. res <- UIO.parMap5(
  178. UIO(readFailures.to(List)),
  179. UIO(readSuccesses.to(List)),
  180. UIO(parseFailures.to(List)),
  181. UIO(parseSuccesses.to(List)),
  182. toIO(
  183. Observable
  184. .fromIterable(parseSuccesses)
  185. .map { case (p, json) => json }
  186. .consumeWith(loadBalancedPluginDataMerger)
  187. ).hideErrors
  188. )(Result.apply)
  189. } yield res
  190. def log(res: Result) =
  191. UIO {
  192. pprint.log(show"Read Successes = ${res.readSuccesses}")
  193. pprint.log(show"Read Failures = ${res.readFailures}")
  194. pprint.log(show"Parse Successes = ${res.parseSuccesses}")
  195. pprint.log(show"Parse Failures = ${res.parseFailures}")
  196. pprint.log(show"Merged = ${res.pluginJson}")
  197. }
  198. case class Result(
  199. readFailures: List[(Plugin, Error)],
  200. readSuccesses: List[(Plugin, String)],
  201. parseFailures: List[(Plugin, ParsingFailure)],
  202. parseSuccesses: List[(Plugin, Json)],
  203. pluginJson: Json
  204. )
  205. object Result {
  206. implicit val show = Show.fromToString[Result]
  207. // implicit val eq = Eq.fromUniversalEquals[Error]
  208. }
  209. }