Skip to content

Commit

Permalink
Merge pull request #1139 from GiganticMinecraft/fix/present
Browse files Browse the repository at this point in the history
プレゼントサブシステムのバグを潰す
  • Loading branch information
kory33 authored Sep 29, 2021
2 parents 40476b8 + e5929ca commit 5b746d7
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import com.github.unchama.minecraft.actions.OnMinecraftServerThread
import com.github.unchama.seichiassist.commands.contextual.builder.BuilderTemplates.playerCommandBuilder
import com.github.unchama.seichiassist.domain.actions.UuidToLastSeenName
import com.github.unchama.seichiassist.subsystems.present.domain.OperationResult.DeleteResult
import com.github.unchama.seichiassist.subsystems.present.domain.{PresentClaimingState, PresentPersistence}
import com.github.unchama.seichiassist.subsystems.present.domain.{GrantRejectReason, PaginationRejectReason, PresentClaimingState, PresentPersistence, RevokeWarning}
import com.github.unchama.seichiassist.util.Util
import com.github.unchama.targetedeffect.commandsender.MessageEffect
import com.github.unchama.targetedeffect.{SequentialEffect, TargetedEffect}
Expand Down Expand Up @@ -70,15 +70,24 @@ class PresentCommand(implicit val ioOnMainThread: OnMinecraftServerThread[IO]) {
_ <- NonServerThreadContextShift[F].shift
state <- persistence.fetchState(context.sender.getUniqueId)
} yield {
val mes = state
val presents = state
.toList
// 配布対象外のプレゼントを除外
.filter { case (_, state) => state != PresentClaimingState.Unavailable }
.map { case (id, state) =>
s"ID=$id: ${decoratePresentState(state)}"
}
.filter(_.nonEmpty)
MessageEffect(mes)

val lines = if (presents.isEmpty) {
List("対象のプレゼントが存在しません")
} else {
List(
s"${ChatColor.GRAY}${ChatColor.UNDERLINE}対象のプレゼント一覧:${ChatColor.RESET}",
) ::: presents
}

MessageEffect(lines)
}

eff.toIO
Expand Down Expand Up @@ -118,11 +127,12 @@ class PresentCommand(implicit val ioOnMainThread: OnMinecraftServerThread[IO]) {
val eff = for {
_ <- NonServerThreadContextShift[F].shift
states <- persistence.fetchStateWithPagination(player, perPage, page)
messageLine = states
.map { case (id, state) =>
s"ID=$id: ${decoratePresentState(state)}"
}
.toList
messageLine = states.fold({
case PaginationRejectReason.TooLargePage(max) =>
List(s"ページ数が大きすぎます。${max}ページ以下にしてください")
}, b => b.map { case (id, state) =>
s"ID=$id: ${decoratePresentState(state)}"
}.toList)
} yield {
MessageEffect(messageLine)
}
Expand Down Expand Up @@ -258,7 +268,7 @@ class PresentCommand(implicit val ioOnMainThread: OnMinecraftServerThread[IO]) {
} yield result match {
case DeleteResult.Done =>
MessageEffect(s"IDが${presentId}のプレゼントの消去は正常に行われました。")
case DeleteResult.NotFount =>
case DeleteResult.NotFound =>
MessageEffect(s"IDが${presentId}のプレゼントは存在しませんでした。")
}

Expand Down Expand Up @@ -312,12 +322,15 @@ class PresentCommand(implicit val ioOnMainThread: OnMinecraftServerThread[IO]) {
globalUUID2Name.keys
else
globalUUID2Name.filter { case (_, name) => restArg.contains(name) }.keys
errorIfNobody = if (target.isEmpty) Some(MessageEffect("対象のプレイヤーが存在しません!")) else None
_ <- persistence.grant(presentId, target.toSet)
errorIfNobody = Option.when(target.isEmpty) { MessageEffect("対象のプレイヤーが存在しません!") }
grantError <- persistence.grant(presentId, target.toSet)
} yield
errorIfNobody.getOrElse(MessageEffect(
errorIfNobody.getOrElse(grantError.map {
case GrantRejectReason.NoSuchPresentID =>
MessageEffect("指定されたプレゼントIDは存在しません!")
}.getOrElse(MessageEffect(
s"プレゼント(id: $presentId)を受け取れるプレイヤーを追加しました。"
))
)))

eff.toIO
} else {
Expand Down Expand Up @@ -369,11 +382,18 @@ class PresentCommand(implicit val ioOnMainThread: OnMinecraftServerThread[IO]) {
else
globalUUID2Name.filter { case (_, name) => restArg.contains(name) }.keys
errorIfNobody = if (target.isEmpty) Some(MessageEffect("対象のプレイヤーが存在しません!")) else None
_ <- persistence.revoke(presentId, target.toSet)
warning <- persistence.revoke(presentId, target.toSet)
} yield {
errorIfNobody.getOrElse(MessageEffect(
s"プレゼント(id: $presentId)を受け取れるプレイヤーを削除しました。"
))
errorIfNobody.getOrElse {
warning.map {
case RevokeWarning.NoSuchPresentID => MessageEffect("そのようなプレゼントIDはありません!")
case RevokeWarning.NoPlayers => MessageEffect("対象となるプレイヤーが存在しません!")
}.getOrElse {
MessageEffect(
s"プレゼント(id: $presentId)を受け取れるプレイヤーを削除しました。"
)
}
}
}
eff.toIO
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.github.unchama.seichiassist.subsystems.present.domain

sealed trait GrantRejectReason

object GrantRejectReason {
case object NoSuchPresentID extends GrantRejectReason
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ object OperationResult {
sealed trait DeleteResult
object DeleteResult {
case object Done extends DeleteResult
case object NotFount extends DeleteResult
case object NotFound extends DeleteResult
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.github.unchama.seichiassist.subsystems.present.domain

sealed trait PaginationRejectReason

object PaginationRejectReason {
case class TooLargePage(exceptedMax: Long) extends PaginationRejectReason
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import java.util.UUID

/**
* プレゼントシステムに関する永続化のインターフェースを規定するトレイト。
* 他で指定がない限り、以下の条件・制約を満たす。違反した時の動作は未定義である:
* - 引数で渡される`PresentID`は対応するプレゼントが存在し、一意でなければならない
* 他で指定がない限り、以下の条件および制約を満たす。違反した時の動作は未定義である:
* - 引数で渡される`PresentID`は対応するプレゼントが存在し、その多重度は1対1である
* - 返り値としての`PresentID`は対応するプレゼントが存在する
*/
trait PresentPersistence[F[_], ItemStack] {
Expand All @@ -31,22 +31,23 @@ trait PresentPersistence[F[_], ItemStack] {
def delete(presentID: PresentID): F[DeleteResult]

/**
* 指定したUUIDを持つプレイヤーに対して`presentID`で指定されたプレゼントを受け取ることができるようにする。
*
* 指定したUUIDを持つプレイヤー群に対して`presentID`で指定されたプレゼントを受け取ることができるようにする。
* このメソッドは同じプレイヤーとプレゼントIDで呼び出された場合はべき等である。また、すでに受取可能なプレイヤーが
* `players`の中に入っていた場合は、そのプレイヤーについての受取可能にする処理をスキップする。
* @param presentID 対象のプレゼントID
* @param players 受け取ることができるようにするプレイヤーのUUID
* @return 永続化層への書き込みを行う作用
*/
def grant(presentID: PresentID, players: Set[UUID]): F[Unit]
def grant(presentID: PresentID, players: Set[UUID]): F[Option[GrantRejectReason]]

/**
* 指定したUUIDを持つプレイヤーが`presentID`で指定されたプレゼントを受け取ることができないようにする。
* 指定したUUIDを持つプレイヤー群が`presentID`で指定されたプレゼントを受け取ることができないようにする。
*
* @param presentID 対象のプレゼントID
* @param players 受け取ることができないようにするプレイヤーのUUID
* @return 永続化層への書き込みを行う作用
*/
def revoke(presentID: PresentID, players: Set[UUID]): F[Unit]
def revoke(presentID: PresentID, players: Set[UUID]): F[Option[RevokeWarning]]

/**
* 永続化層でプレゼントを受け取ったことにする。
Expand All @@ -66,7 +67,8 @@ trait PresentPersistence[F[_], ItemStack] {

/**
* ページネーション付きでプレイヤーがプレゼントを受け取ることができるかどうか列挙する。
* このときの出現順序は、[[PresentID]]が最も若いエントリから先に出現する。
* このときのページネーションは、[[PresentID]]が最も若いエントリから先に出現するように行われる。
* また、ページネーションされたListの中での出現順序も、[[PresentID]]が最も若いエントリから先に出現する。
*
* 例として以下のような状況を仮定する:
* - 既知のPresentIDとItemStackのエントリ: `List((1, aaa), (3, ccc), (6, fff), (4, ddd), (5, eee), (2, bbb))`
Expand All @@ -77,21 +79,27 @@ trait PresentPersistence[F[_], ItemStack] {
*
* この時 `pp.mappingWithPagination(A, 1, 5)` を呼び出すと、作用の中で計算される結果は次のとおりになる:
*
* `Map(1 -> Claimed, 2 -> Claimed, 3 -> Claimed, 4 -> NotClaimed, 5 -> Unavailable)`
* `List(1 -> Claimed, 2 -> Claimed, 3 -> Claimed, 4 -> NotClaimed, 5 -> Unavailable)`
*
* 備考:
* - 実装によっては、[[fetchState]]などを呼び出して既知のエントリを全列挙する可能性がある
* - 実装によっては、[[fetchState]]などを呼び出して有効なエントリを全列挙する可能性がある
* - このメソッドは一貫性のために[[fetchState]]のドキュメントにある制約を継承する。
* - 最終インデックスが有効なプレゼントの総数を超えるとき、作用はLeftを返さなければならない。
* - 最終インデックスが有効なプレゼントの総数を超えないとき、作用はRightを返さなければならない。
*
* @param player 調べる対象のプレイヤー
* @param perPage ページごとのエントリの数
* @param page ページ、1オリジン
* @return ページネーションを計算して返す作用
*/
def fetchStateWithPagination(player: UUID, perPage: Int Refined Positive, page: Int Refined Positive): F[Map[PresentID, PresentClaimingState]]
def fetchStateWithPagination(
player: UUID,
perPage: Int Refined Positive,
page: Int Refined Positive
): F[Either[PaginationRejectReason, List[(PresentID, PresentClaimingState)]]]

/**
* プレイヤーがプレゼントを受け取ることができるかどうか列挙する。このとき、計算されるMapは次の性質を持つ:
* プレイヤーがプレゼントを受け取ることができるかどうか列挙する。このとき、計算されるMapは次の性質を満たす:
*
* - すでに受け取ったプレゼントに対応するPresentIDに対して[[PresentClaimingState.Claimed]]がマッピングされる
* - 受け取ることができるが、まだ受け取っていないプレゼントに対応するPresentIDに対して[[PresentClaimingState.NotClaimed]]がマッピングされる
Expand All @@ -104,7 +112,7 @@ trait PresentPersistence[F[_], ItemStack] {
def fetchState(player: UUID): F[Map[PresentID, PresentClaimingState]]

/**
* 指定したプレゼントIDからプレゼントを引き出す
* 指定したプレゼントIDでプレゼントを検索する
*
* @param presentID プレゼントID
* @return 存在する場合は`Some[ItemStack]`、存在しない場合は`None`を返す作用
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.github.unchama.seichiassist.subsystems.present.domain

sealed trait RevokeWarning

object RevokeWarning {
case object NoSuchPresentID extends RevokeWarning
case object NoPlayers extends RevokeWarning
}
Loading

0 comments on commit 5b746d7

Please sign in to comment.