forked from nova/jmonkey-test
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
230 lines
6.8 KiB
package wow.doge.mygame.subsystems.moddingsystem
|
|
import java.io.FileNotFoundException
|
|
import java.nio.file.NoSuchFileException
|
|
|
|
import scala.collection.View
|
|
import scala.collection.immutable.ArraySeq
|
|
|
|
import cats.Show
|
|
import cats.implicits._
|
|
import cats.kernel.Eq
|
|
import io.circe._
|
|
import io.circe.generic.JsonCodec
|
|
import io.circe.generic.semiauto._
|
|
import io.circe.parser._
|
|
import monix.bio.IO
|
|
import monix.bio.UIO
|
|
import monix.reactive.Consumer
|
|
import monix.reactive.Observable
|
|
import wow.doge.mygame.utils.IOUtils
|
|
|
|
import IOUtils.toIO
|
|
|
|
@JsonCodec
|
|
final case class Test1(hello1: String, hello2: String)
|
|
@JsonCodec
|
|
final case class Test2(hello1: String)
|
|
final case class Plugin(name: String, priority: Int)
|
|
object Plugin {
|
|
implicit val decoder: Decoder[Plugin] = deriveDecoder
|
|
implicit val show = Show.fromToString[Plugin]
|
|
implicit val eq = Eq.fromUniversalEquals[Plugin]
|
|
}
|
|
|
|
object ModdingSystem {
|
|
sealed trait Error
|
|
final case class CouldNotDecode(cause: String) extends Error
|
|
final case class ParseFailure(cause: io.circe.ParsingFailure) extends Error
|
|
final case class DecodingFailure(cause: io.circe.DecodingFailure)
|
|
extends Error
|
|
final case class FileNotFound(path: os.Path) extends Error
|
|
object Error {
|
|
implicit val show = Show.fromToString[Error]
|
|
implicit val eq = Eq.fromUniversalEquals[Error]
|
|
}
|
|
|
|
def readPluginsList(dir: os.Path): IO[Error, ArraySeq[Plugin]] =
|
|
IO(parse(os.read(dir / "plugins.json")))
|
|
.onErrorHandleWith {
|
|
case _: FileNotFoundException =>
|
|
IO.raiseError(FileNotFound(dir / "plugins.json"))
|
|
case _: NoSuchFileException =>
|
|
IO.raiseError(FileNotFound(dir / "plugins.json"))
|
|
}
|
|
.flatMap(files =>
|
|
IO.fromEither(files)
|
|
.map(_.as[ArraySeq[Plugin]])
|
|
.mapError(ParseFailure)
|
|
)
|
|
.flatMap(result => IO.fromEither(result).mapError(DecodingFailure))
|
|
|
|
def findPluginFiles(dir: os.Path): View[os.Path] =
|
|
os.list(dir)
|
|
.view
|
|
.filter(f => f.ext == "json" && f.baseName.endsWith("plugin"))
|
|
|
|
def findAndReadPluginFiles(
|
|
dir: os.Path,
|
|
plugins: ArraySeq[Plugin]
|
|
): UIO[(View[(Plugin, Error)], View[(Plugin, String)])] =
|
|
UIO(
|
|
plugins
|
|
.sortBy(_.priority)
|
|
.view
|
|
.map { p =>
|
|
val path = dir / os.RelPath(p.name + ".plugin.json")
|
|
p ->
|
|
Either
|
|
.catchNonFatal(os.read(path))
|
|
.leftMap {
|
|
case _: FileNotFoundException => FileNotFound(path)
|
|
case _: NoSuchFileException => FileNotFound(path)
|
|
}
|
|
|
|
}
|
|
.partitionMap {
|
|
case (p, either) =>
|
|
either match {
|
|
case Left(value) => Left(p -> value)
|
|
case Right(value) => Right(p -> value)
|
|
}
|
|
}
|
|
)
|
|
// : (View[(Plugin, Error)], View[(Plugin, String)])
|
|
def findAndReadPluginFiles2(
|
|
dir: os.Path,
|
|
plugins: ArraySeq[Plugin]
|
|
) =
|
|
// IO.parTraverse(plugins.sortBy(_.priority))(p =>
|
|
// IO {
|
|
// val path = dir / os.RelPath(p.name + ".plugin.json")
|
|
// os.read(path)
|
|
// }
|
|
// .onErrorHandleWith {
|
|
// case _: FileNotFoundException =>
|
|
// IO.raiseError(FileNotFound(dir.toString))
|
|
// case _: NoSuchFileException =>
|
|
// IO.raiseError(FileNotFound(dir.toString))
|
|
// }
|
|
// .flatMap(r => UIO(p -> r))
|
|
// ).map {
|
|
// _.partitionMap {
|
|
// case (p, either) =>
|
|
// either match {
|
|
// case Left(value) => Left(p -> value)
|
|
// case Right(value) => Right(p -> value)
|
|
// }
|
|
// }
|
|
// }
|
|
plugins
|
|
.sortBy(_.priority)
|
|
.view
|
|
.map { p =>
|
|
val path = dir / os.RelPath(p.name + ".plugin.json")
|
|
p -> IO(os.read(path))
|
|
.onErrorHandleWith {
|
|
case _: FileNotFoundException => IO.raiseError(FileNotFound(path))
|
|
case _: NoSuchFileException => IO.raiseError(FileNotFound(path))
|
|
}
|
|
// .map(r => p -> r)
|
|
|
|
}
|
|
.map {
|
|
case (p, io) =>
|
|
io.attempt.map {
|
|
case Left(value) => Left(p -> value)
|
|
case Right(value) => Right(p -> value)
|
|
}
|
|
}
|
|
.to(List)
|
|
.parSequence
|
|
|
|
// .partitionMap {
|
|
// _.map {
|
|
// case l @ Left(value) => l
|
|
// case r @ Right(value) => r
|
|
// }
|
|
// }
|
|
// .sequence
|
|
|
|
// def readPluginFiles(filePaths: View[os.Path]) =
|
|
// filePaths.map(path => os.read(path))
|
|
|
|
// def readPluginFiles2(filePaths: View[os.Path]) =
|
|
// filePaths
|
|
// .map(path =>
|
|
// IO(os.read(path)).onErrorHandleWith {
|
|
// case _: FileNotFoundException =>
|
|
// IO.raiseError(FileNotFound(path))
|
|
// case _: NoSuchFileException =>
|
|
// IO.raiseError(FileNotFound(path))
|
|
// }
|
|
// )
|
|
// .to(List)
|
|
// .parSequence
|
|
def parsePluginFiles(files: View[(Plugin, String)]) =
|
|
files
|
|
.map {
|
|
case (p, s) => p -> parse(s)
|
|
}
|
|
.partitionMap {
|
|
case (p, Left(value)) => Left(p -> value)
|
|
case (p, Right(value)) => Right(p -> value)
|
|
}
|
|
|
|
val emptyJson = Json.fromString("empty")
|
|
|
|
val foldFn: (Json, Json) => Json = {
|
|
case (json, Json.Null) => json //ignore null values
|
|
case (json, value) => json.deepMerge(value)
|
|
}
|
|
|
|
def mergePluginDataConsumer =
|
|
Consumer.foldLeft[Json, Json](emptyJson)(foldFn)
|
|
|
|
def loadBalancedPluginDataMerger =
|
|
Consumer
|
|
.loadBalance(parallelism = 2, mergePluginDataConsumer)
|
|
.map(_.foldLeft(emptyJson)(foldFn))
|
|
|
|
def run(wd: os.Path = os.pwd) =
|
|
for {
|
|
plugins <- readPluginsList(wd)
|
|
(readFailures, readSuccesses) <- findAndReadPluginFiles(wd, plugins)
|
|
(parseFailures, parseSuccesses) <- UIO(parsePluginFiles(readSuccesses))
|
|
res <- UIO.parMap5(
|
|
UIO(readFailures.to(List)),
|
|
UIO(readSuccesses.to(List)),
|
|
UIO(parseFailures.to(List)),
|
|
UIO(parseSuccesses.to(List)),
|
|
toIO(
|
|
Observable
|
|
.fromIterable(parseSuccesses)
|
|
.map { case (p, json) => json }
|
|
.consumeWith(loadBalancedPluginDataMerger)
|
|
).hideErrors
|
|
)(Result.apply)
|
|
} yield res
|
|
|
|
def log(res: Result) =
|
|
UIO {
|
|
pprint.log(show"Read Successes = ${res.readSuccesses}")
|
|
pprint.log(show"Read Failures = ${res.readFailures}")
|
|
pprint.log(show"Parse Successes = ${res.parseSuccesses}")
|
|
pprint.log(show"Parse Failures = ${res.parseFailures}")
|
|
pprint.log(show"Merged = ${res.pluginJson}")
|
|
}
|
|
|
|
case class Result(
|
|
readFailures: List[(Plugin, Error)],
|
|
readSuccesses: List[(Plugin, String)],
|
|
parseFailures: List[(Plugin, ParsingFailure)],
|
|
parseSuccesses: List[(Plugin, Json)],
|
|
pluginJson: Json
|
|
)
|
|
object Result {
|
|
implicit val show = Show.fromToString[Result]
|
|
// implicit val eq = Eq.fromUniversalEquals[Error]
|
|
}
|
|
|
|
}
|