|
@@ -12,7 +12,6 @@ import org.springframework.scheduling.annotation.Scheduled
|
|
|
import org.springframework.stereotype.Service
|
|
|
import java.util.*
|
|
|
import java.util.concurrent.CompletableFuture
|
|
|
-import java.util.concurrent.LinkedBlockingQueue
|
|
|
import java.util.function.Consumer
|
|
|
import javax.annotation.PostConstruct
|
|
|
import kotlin.math.min
|
|
@@ -33,13 +32,16 @@ class DemoInMem {
|
|
|
|
|
|
@PostConstruct
|
|
|
fun init() {
|
|
|
- emitter.emit(1000, CoinValue.one)
|
|
|
- emitter.emit(500, CoinValue.three)
|
|
|
- emitter.emit(200, CoinValue.five)
|
|
|
- emitter.emit(100, CoinValue.ten)
|
|
|
- emitter.emit(50, CoinValue.quarter)
|
|
|
- emitter.emit(10, CoinValue.half)
|
|
|
- emitter.emit(5, CoinValue.full)
|
|
|
+ emitter.emit(10000, CoinValue.one)
|
|
|
+ emitter.emit(5000, CoinValue.three)
|
|
|
+ emitter.emit(2000, CoinValue.five)
|
|
|
+ emitter.emit(1000, CoinValue.ten)
|
|
|
+ emitter.emit(500, CoinValue.quarter)
|
|
|
+ emitter.emit(100, CoinValue.half)
|
|
|
+ emitter.emit(50, CoinValue.full)
|
|
|
+ emitter.emit(25, CoinValue.bi)
|
|
|
+ emitter.emit(10, CoinValue.mega)
|
|
|
+
|
|
|
bank.exchange(bank.getSelfAccount(), emitter, 1_300_000)
|
|
|
(0 until 100).forEach { personMap.computeIfAbsent(UUID.randomUUID()) { id -> DemoHuman(id, Nomen.randomName()) } }
|
|
|
personMap.keys.map { bank.addAccount(it) }.forEach {
|
|
@@ -72,7 +74,7 @@ class GlobalBank : Bank {
|
|
|
|
|
|
val accountMap = mutableMapOf<UUID, DemoAccount>()
|
|
|
val personToAccountTable = mutableSetOf<Pair<UUID, UUID>>()
|
|
|
- val globalQueue: Queue<BankEvent> = LinkedBlockingQueue()
|
|
|
+ val globalQueue: Deque<BankEvent> = ArrayDeque()
|
|
|
val globalFutureMap = mutableMapOf<UUID, CompletableDeferred<Any>>()
|
|
|
|
|
|
private lateinit var selfId: UUID
|
|
@@ -121,22 +123,44 @@ class GlobalBank : Bank {
|
|
|
def.invokeOnCompletion { t: Throwable? -> if (t == null) ret.complete(null) else ret.completeExceptionally(t) }
|
|
|
def
|
|
|
}
|
|
|
- globalQueue.offer(e)
|
|
|
+ globalQueue.offerLast(e)
|
|
|
return ret
|
|
|
}
|
|
|
|
|
|
- suspend fun pollLater(e: BankEvent) {
|
|
|
- when (e) {
|
|
|
+ suspend fun pollLater(e: BankEvent): Boolean {
|
|
|
+ return when (e) {
|
|
|
is ExchangeStartEvent -> {
|
|
|
if (e.from.overall < e.amount) {
|
|
|
- globalQueue.offer(ExchangeFailedEvent(UUID.randomUUID(), e, "not enough overall"))
|
|
|
+ globalQueue.offerLast(ExchangeFailedEvent(UUID.randomUUID(), e, "not enough overall"))
|
|
|
} else {
|
|
|
val coinsMoreOrExact = e.from.extractMoreOrExact(e.amount)
|
|
|
- val coinsExactAndCashback = coinsMoreOrExact
|
|
|
- e.to.accept(coinsExactAndCashback)
|
|
|
- //e.from.accept(coinsExactAndCashback.second)
|
|
|
- emitter.free(coinsExactAndCashback)
|
|
|
- globalQueue.offer(ExchangeSuccessEvent(UUID.randomUUID(), e))
|
|
|
+ val coinsExact = coinsMoreOrExact.filter { it.id != null }
|
|
|
+ val coinsCashback = coinsMoreOrExact.filter { it.id == null }
|
|
|
+ val retry = if (coinsCashback.isNotEmpty()) {
|
|
|
+ if (e.to.id == selfAccount.id && e.from.id == emitter.id) {
|
|
|
+ false
|
|
|
+ } else if (e.from.id == selfAccount.id) {
|
|
|
+ true
|
|
|
+ } else {
|
|
|
+ TODO()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ false
|
|
|
+ }
|
|
|
+ if (!retry) {
|
|
|
+ e.to.accept(coinsExact)
|
|
|
+ emitter.free(coinsExact)
|
|
|
+ globalQueue.offerLast(ExchangeSuccessEvent(UUID.randomUUID(), e))
|
|
|
+ true
|
|
|
+ } else {
|
|
|
+ e.from.accept(coinsExact)
|
|
|
+ coinsCashback.map { it as DemoCashbackCoin }.map { it.invert() }.forEach {
|
|
|
+ val coinId = emitter.emitOne(it)
|
|
|
+ val coin = emitter.extractOne(coinId)
|
|
|
+ e.from.accept(listOf(coin))
|
|
|
+ }
|
|
|
+ false
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
is ExchangeSuccessEvent -> {
|
|
@@ -144,22 +168,25 @@ class GlobalBank : Bank {
|
|
|
globalFutureMap.remove(e.parentEvent.id)
|
|
|
it.complete(true)
|
|
|
}
|
|
|
+ true
|
|
|
}
|
|
|
is ExchangeFailedEvent -> {
|
|
|
globalFutureMap.get(e.parentEvent.id)?.let {
|
|
|
globalFutureMap.remove(e.parentEvent.id)
|
|
|
it.completeExceptionally(ExchangeFailedException(e, e.reason))
|
|
|
}
|
|
|
+ true
|
|
|
}
|
|
|
else -> throw IllegalArgumentException("wrong event type ${e.javaClass.name}")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fun poll(e: BankEvent) = runBlocking {
|
|
|
+ fun poll(e: BankEvent): Boolean = runBlocking {
|
|
|
try {
|
|
|
pollLater(e)
|
|
|
} catch (t: Throwable) {
|
|
|
log.error("error in event", t)
|
|
|
+ true
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -176,8 +203,10 @@ class GlobalBank : Bank {
|
|
|
fun tick() {
|
|
|
var pollCount = 0
|
|
|
while (globalQueue.isNotEmpty() && pollCount < maxPollCount) {
|
|
|
- val e = globalQueue.poll()
|
|
|
- poll(e)
|
|
|
+ val e = globalQueue.element()
|
|
|
+ if (poll(e)) {
|
|
|
+ globalQueue.remove(e)
|
|
|
+ }
|
|
|
pollCount++
|
|
|
log.info("poll")
|
|
|
}
|
|
@@ -202,6 +231,7 @@ class ExchangeStartEvent(eventId: UUID, val to: BankAccount, val from: BankAccou
|
|
|
@Service
|
|
|
class DemoInMemEmitter : GlobalEmitter {
|
|
|
|
|
|
+ val globalId = UUID.fromString("a671cdca-782a-4caf-8aad-056f6b62d822")
|
|
|
val coinMap = mutableMapOf<UUID, Coin>()
|
|
|
val coinExtractedSet = mutableSetOf<UUID>()
|
|
|
val coinFreeSet = mutableSetOf<UUID>()
|
|
@@ -216,10 +246,21 @@ class DemoInMemEmitter : GlobalEmitter {
|
|
|
TODO("not implemented")
|
|
|
}
|
|
|
|
|
|
+ override fun getId(): UUID {
|
|
|
+ return globalId
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun extractOne(coinId: UUID): Coin {
|
|
|
+ val coin = coinMap.get(coinId)!!
|
|
|
+ coinExtractedSet.add(coin.id)
|
|
|
+ return coin
|
|
|
+ }
|
|
|
+
|
|
|
override fun extractMoreOrExact(amount: Long): MutableCollection<Coin> {
|
|
|
val indoorCoins = coinMap.values.filter { !coinExtractedSet.contains(it.id) }
|
|
|
val ret = CoinUtils.mergeCoins(indoorCoins, amount)
|
|
|
- ret.forEach { coinExtractedSet.add(it.id) }
|
|
|
+ val realCoins = ret.filter { it.id != null }
|
|
|
+ realCoins.forEach { coinExtractedSet.add(it.id) }
|
|
|
return ret.toMutableList()
|
|
|
}
|
|
|
|
|
@@ -240,7 +281,7 @@ class DemoInMemEmitter : GlobalEmitter {
|
|
|
}
|
|
|
|
|
|
private suspend fun calcLater() {
|
|
|
- coinFreeSet.map { coinMap[it] }.filterNotNull().map { it as DemoCoin }.forEach { it.era = min(maxEraValue, it.era + 1) }
|
|
|
+ coinFreeSet.map { coinMap[it] }.filterNotNull().map { it as DemoCoin }.forEach { it._era = min(maxEraValue, it._era + 1) }
|
|
|
}
|
|
|
|
|
|
override fun calc(): CompletableFuture<Any> {
|
|
@@ -259,21 +300,30 @@ class DemoInMemEmitter : GlobalEmitter {
|
|
|
}
|
|
|
return ret
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-val maxEraValue = 364
|
|
|
+ override fun emitOne(coin: Coin): UUID {
|
|
|
+ val new = DemoCoin(UUID.randomUUID(), coin.value, coin.era)
|
|
|
+ coinMap.put(new.id, new)
|
|
|
+ return new.id
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
-class DemoCoin(val coinId: UUID, val initialValue: CoinValue) : Coin {
|
|
|
+val maxEraValue: Int = CoinValue.MAX_ERA.toInt()
|
|
|
|
|
|
- var era = 1
|
|
|
- var delta: Long = initialValue.amount / maxEraValue
|
|
|
+class DemoCoin(val coinId: UUID, val initialValue: CoinValue, var _era: Int = 1) : Coin {
|
|
|
|
|
|
override fun getId(): UUID = coinId
|
|
|
|
|
|
override fun getValue(): CoinValue = initialValue
|
|
|
|
|
|
+ /*
|
|
|
override fun getCurrent(): Long {
|
|
|
- return initialValue.amount - ((era - 1) * delta)
|
|
|
+ return initialValue.amount - ((_era - 1) * initialValue.delta)
|
|
|
+ }
|
|
|
+ */
|
|
|
+
|
|
|
+ override fun getEra(): Int {
|
|
|
+ return _era
|
|
|
}
|
|
|
|
|
|
override fun toString(): String {
|
|
@@ -281,6 +331,59 @@ class DemoCoin(val coinId: UUID, val initialValue: CoinValue) : Coin {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+class DemoCashbackCoin(val _value: CoinValue, val cashback: Long, val _era: Int) : Coin {
|
|
|
+
|
|
|
+ init {
|
|
|
+ if (cashback >= 0) throw IllegalArgumentException("cashback must be negative")
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun getEra(): Int {
|
|
|
+ return _era
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun getId(): UUID? {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun getValue(): CoinValue {
|
|
|
+ return _value
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun getCurrent(): Long {
|
|
|
+ return cashback
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun toString(): String {
|
|
|
+ return "${value} jdc${era} (${current})"
|
|
|
+ }
|
|
|
+
|
|
|
+ fun invert(): DemoFutureCoin {
|
|
|
+ val ret = DemoFutureCoin(_value, Math.max(1, maxEraValue - _era + 1))
|
|
|
+ if (ret.current != value.amount + cashback)
|
|
|
+ throw IllegalArgumentException("wrong inverted coin")
|
|
|
+ return ret
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class DemoFutureCoin(val _value: CoinValue, val _era: Int) : Coin {
|
|
|
+
|
|
|
+ override fun getId(): UUID? {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun getValue(): CoinValue {
|
|
|
+ return _value
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun getEra(): Int {
|
|
|
+ return _era
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun toString(): String {
|
|
|
+ return "${value} jdf${era} (${current})"
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
class DemoHuman(val selfId: UUID, name: String) : NaturalPerson {
|
|
|
|
|
|
override fun getId(): UUID = selfId
|
|
@@ -289,11 +392,24 @@ class DemoHuman(val selfId: UUID, name: String) : NaturalPerson {
|
|
|
|
|
|
class DemoSelfAccount(val selfId: UUID) : DemoAccount(selfId, selfId)
|
|
|
|
|
|
-open class DemoAccount(val id: UUID, personId: UUID) : BankAccount {
|
|
|
+open class DemoAccount(val accountId: UUID, personId: UUID) : BankAccount {
|
|
|
|
|
|
val internalCoins = mutableMapOf<UUID, Coin>()
|
|
|
var _overall: Long? = null
|
|
|
|
|
|
+ override fun getId(): UUID {
|
|
|
+ return accountId
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun extractOne(coinId: UUID): Coin {
|
|
|
+ return if (internalCoins.containsKey(coinId)) {
|
|
|
+ val coin = internalCoins.remove(coinId)!!
|
|
|
+ coin
|
|
|
+ } else {
|
|
|
+ throw IllegalArgumentException("no such coin")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
override fun getOverall(): Long {
|
|
|
return if (_overall != null) {
|
|
|
_overall
|
|
@@ -331,6 +447,7 @@ open class DemoAccount(val id: UUID, personId: UUID) : BankAccount {
|
|
|
}
|
|
|
|
|
|
override fun extractMoreOrExact(amount: Long): MutableCollection<Coin> {
|
|
|
+
|
|
|
val coins = CoinUtils.mergeCoins(internalCoins.values, amount).toMutableList()
|
|
|
coins.forEach {
|
|
|
internalCoins.remove(it.id)
|
|
@@ -345,39 +462,85 @@ class CoinUtils {
|
|
|
|
|
|
companion object {
|
|
|
|
|
|
+ fun makeSomeFake(cashback: Long): Collection<Coin> {
|
|
|
+ val ret = mutableListOf<Coin>()
|
|
|
+ var totalCashback = cashback
|
|
|
+ do {
|
|
|
+ val value = CoinValue.values().filter { it.amount >= Math.abs(totalCashback) }.sortedBy { it.amount }.first()
|
|
|
+ val valueByEra = (1..maxEraValue).map { era -> era to (value.amount - ((era - 1) * value.delta)) }.filter { it.second <= Math.abs(totalCashback) }.first()
|
|
|
+ ret.add(DemoCashbackCoin(value, -valueByEra.second, valueByEra.first))
|
|
|
+ totalCashback += valueByEra.second
|
|
|
+ } while (totalCashback < 0)
|
|
|
+ return ret
|
|
|
+ }
|
|
|
+
|
|
|
fun mergeCoins(coins: Collection<Coin>, amount: Long): Collection<Coin> {
|
|
|
- var totalAmount = 0L
|
|
|
- val sortedCoins = coins.sortedBy { it.current }
|
|
|
- val position = sortedCoins.binarySearch { it.current.compareTo(amount) }
|
|
|
val ret = mutableListOf<Coin>()
|
|
|
- if (position == 0) {
|
|
|
- TODO()
|
|
|
- } else if (position > 0) {
|
|
|
- val coinL = sortedCoins.get(position)
|
|
|
- val coinR = sortedCoins.get(position - 1)
|
|
|
- if (coinL.current == amount) {
|
|
|
- ret.add(coinL)
|
|
|
- } else if (coinR.current == amount) {
|
|
|
- ret.add(coinR)
|
|
|
- } else {
|
|
|
- TODO()
|
|
|
- }
|
|
|
- } else if (position < 0) {
|
|
|
- val actualPosition = -(position + 1)
|
|
|
- if (actualPosition == 0) {
|
|
|
- TODO()
|
|
|
- } else if (actualPosition < sortedCoins.size) {
|
|
|
- TODO()
|
|
|
+ val coinMap = coins.groupBy { it.current }.map { it.key to it.value.map { it.id }.toMutableList() }.toMap().toMutableMap()
|
|
|
+ val usedCoins = mutableSetOf<UUID>()
|
|
|
+ var totalAmount = 0L
|
|
|
+ do {
|
|
|
+ val diffAmount = amount - totalAmount
|
|
|
+ val leastCoinMap = coinMap.map { it.key to it.value.subtract(usedCoins).toList() }.filter { it.second.isNotEmpty() }.toMap()
|
|
|
+ val loeCoinMap = leastCoinMap.filter { it.key <= diffAmount }.map { it.key to it.value }.sortedByDescending { it.first }
|
|
|
+ if (loeCoinMap.isNotEmpty()) {
|
|
|
+ val biggestLoe = loeCoinMap.first().first
|
|
|
+ totalAmount += biggestLoe
|
|
|
+ val coinId = loeCoinMap.first().second.first()
|
|
|
+ usedCoins.add(coinId)
|
|
|
} else {
|
|
|
- var idx = sortedCoins.size - 1
|
|
|
- do {
|
|
|
- val coin = sortedCoins.get(idx)
|
|
|
- totalAmount = totalAmount + coin.current
|
|
|
- ret.add(coin)
|
|
|
- idx--
|
|
|
- } while (totalAmount < amount)
|
|
|
+ val smallestGt = leastCoinMap.keys.sorted().firstOrNull()
|
|
|
+ if (smallestGt != null) {
|
|
|
+ val cashbackAmount = diffAmount - smallestGt
|
|
|
+ totalAmount += smallestGt
|
|
|
+ val coinId = leastCoinMap.get(smallestGt)?.first()!!
|
|
|
+ usedCoins.add(coinId)
|
|
|
+ ret.addAll(makeSomeFake(cashbackAmount))
|
|
|
+ } else {
|
|
|
+ TODO()
|
|
|
+ }
|
|
|
}
|
|
|
+ } while (totalAmount < amount)
|
|
|
+ ret.addAll(coins.filter { usedCoins.contains(it.id) })
|
|
|
+ val realAmount = ret.filter { it.id != null }.map { it.current }.sum()
|
|
|
+ if (totalAmount != realAmount) {
|
|
|
+ TODO()
|
|
|
+ }
|
|
|
+ val fakeAmount = ret.map { it.current }.sum()
|
|
|
+ if (fakeAmount != amount) {
|
|
|
+ TODO()
|
|
|
}
|
|
|
+ /* val sortedCoins = coins.sortedBy { it.current }
|
|
|
+ val position = sortedCoins.binarySearch { it.current.compareTo(amount) }
|
|
|
+ val ret = mutableListOf<Coin>()
|
|
|
+ if (position == 0) {
|
|
|
+ TODO()
|
|
|
+ } else if (position > 0) {
|
|
|
+ val coinL = sortedCoins.get(position)
|
|
|
+ val coinR = sortedCoins.get(position - 1)
|
|
|
+ if (coinL.current == amount) {
|
|
|
+ ret.add(coinL)
|
|
|
+ } else if (coinR.current == amount) {
|
|
|
+ ret.add(coinR)
|
|
|
+ } else {
|
|
|
+ TODO()
|
|
|
+ }
|
|
|
+ } else if (position < 0) {
|
|
|
+ val actualPosition = -(position + 1)
|
|
|
+ if (actualPosition == 0) {
|
|
|
+ TODO()
|
|
|
+ } else if (actualPosition < sortedCoins.size) {
|
|
|
+ TODO()
|
|
|
+ } else {
|
|
|
+ var idx = sortedCoins.size - 1
|
|
|
+ do {
|
|
|
+ val coin = sortedCoins.get(idx)
|
|
|
+ totalAmount = totalAmount + coin.current
|
|
|
+ ret.add(coin)
|
|
|
+ idx--
|
|
|
+ } while (totalAmount < amount)
|
|
|
+ }
|
|
|
+ } */
|
|
|
return ret
|
|
|
}
|
|
|
}
|