RealmWriteService + FlatTimeInterval for better performance

threading
Laurent 3 years ago
parent 526e50f8e4
commit 929365fc4c
  1. 4
      app/src/main/java/net/pokeranalytics/android/RealmWriteService.kt
  2. 262
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  3. 5
      app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt
  4. 16
      app/src/main/java/net/pokeranalytics/android/calculus/ReportWhistleBlower.kt
  5. 2
      app/src/main/java/net/pokeranalytics/android/model/extensions/SessionExtensions.kt
  6. 7
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  7. 13
      app/src/main/java/net/pokeranalytics/android/model/migrations/PokerAnalyticsMigration.kt
  8. 61
      app/src/main/java/net/pokeranalytics/android/model/realm/FlatTimeInterval.kt
  9. 25
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  10. 247
      app/src/main/java/net/pokeranalytics/android/model/utils/SessionSetManager.kt
  11. 3
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatisticsFragment.kt
  12. 1
      app/src/main/java/net/pokeranalytics/android/ui/fragment/report/ComposableTableReportFragment.kt
  13. 8
      app/src/main/java/net/pokeranalytics/android/ui/modules/calendar/CalendarFragment.kt
  14. 27
      app/src/main/java/net/pokeranalytics/android/ui/modules/feed/FeedFragment.kt
  15. 26
      app/src/main/java/net/pokeranalytics/android/util/extensions/DateExtension.kt

@ -43,9 +43,9 @@ class RealmWriteService : Service() {
this.realm.executeTransactionAsync({ asyncRealm ->
handler(asyncRealm)
Timber.d(">> handler done")
// Timber.d(">> handler done")
}, {
Timber.d(">> YEAAAAAAAAAAAH !!!")
// Timber.d(">> YEAAAAAAAAAAAH !!!")
this.realm.refresh()
}, {
Timber.d(">> NOOOOO error = $it")

@ -6,10 +6,10 @@ import net.pokeranalytics.android.calculus.Stat.*
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.Criteria
import net.pokeranalytics.android.model.combined
import net.pokeranalytics.android.model.extensions.hourlyDuration
import net.pokeranalytics.android.model.filter.Query
import net.pokeranalytics.android.model.filter.filter
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.utils.SessionInterval
import net.pokeranalytics.android.util.extensions.startOfDay
import timber.log.Timber
import java.util.*
@ -45,8 +45,18 @@ class Calculator {
filter: Filter? = null,
aggregationType: AggregationType? = null,
userGenerated: Boolean = false,
reportSetupId: String? = null) :
this(progressValues, stats, criterias, filter?.query ?: Query(), filter?.id, aggregationType, userGenerated, reportSetupId)
reportSetupId: String? = null
) :
this(
progressValues,
stats,
criterias,
filter?.query ?: Query(),
filter?.id,
aggregationType,
userGenerated,
reportSetupId
)
/**
* Specifies whether progress values should be added and their kind
@ -111,6 +121,7 @@ class Calculator {
get() {
return this.stats.contains(LOCATIONS_PLAYED)
}
/**
* Whether the number of days played should be computed
*/
@ -118,6 +129,7 @@ class Calculator {
get() {
return this.stats.contains(DAYS_PLAYED)
}
/**
* Whether progress values should be managed at the group level
*/
@ -167,7 +179,11 @@ class Calculator {
}
return when (aggregationType) {
AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(realm, listOf(group), options)
AggregationType.SESSION, AggregationType.DURATION -> this.computeGroups(
realm,
listOf(group),
options
)
AggregationType.MONTH, AggregationType.YEAR -> {
this.computeStats(realm, options)
}
@ -198,7 +214,11 @@ class Calculator {
/**
* Computes all statIds for list of Session sessionGroup
*/
fun computeGroups(realm: Realm, groups: List<ComputableGroup>, options: Options = Options()): Report {
fun computeGroups(
realm: Realm,
groups: List<ComputableGroup>,
options: Options = Options()
): Report {
val report = Report(options)
for (group in groups) {
@ -236,9 +256,14 @@ class Calculator {
/**
* Computes statIds for a SessionSet
*/
fun compute(realm: Realm, computableGroup: ComputableGroup, options: Options = Options()): ComputedResults {
fun compute(
realm: Realm,
computableGroup: ComputableGroup,
options: Options = Options()
): ComputedResults {
val results = ComputedResults(computableGroup, options.shouldManageMultiGroupProgressValues)
val results =
ComputedResults(computableGroup, options.shouldManageMultiGroupProgressValues)
val computables = computableGroup.computables(realm, options.shouldSortValues)
@ -252,32 +277,39 @@ class Calculator {
// Timber.d("$$$ buyin = ${it.ratedBuyin} $$$ net result = ${it.ratedNet}")
// }
val d1 = Date()
var ratedNet = computables.sum(ComputableResult.Field.RATED_NET.identifier).toDouble()
if (options.includedTransactions.isNotEmpty()) {
for (transactionType in options.includedTransactions) {
val transactions = computableGroup.transactions(realm, transactionType, options.shouldSortValues)
val transactionRatedAmount = transactions.sum(Transaction.Field.RATED_AMOUNT.identifier).toDouble()
val transactions = computableGroup.transactions(
realm,
transactionType,
options.shouldSortValues
)
val transactionRatedAmount =
transactions.sum(Transaction.Field.RATED_AMOUNT.identifier).toDouble()
ratedNet += transactionRatedAmount
}
}
results.addStat(NET_RESULT, ratedNet)
val totalHands = computables.sum(ComputableResult.Field.ESTIMATED_HANDS.identifier).toDouble()
val totalHands =
computables.sum(ComputableResult.Field.ESTIMATED_HANDS.identifier).toDouble()
results.addStat(HANDS_PLAYED, totalHands)
val bbSum = computables.sum(ComputableResult.Field.BB_NET.identifier).toDouble()
results.addStat(BB_NET_RESULT, bbSum)
val bbSessionCount = computables.sum(ComputableResult.Field.HAS_BIG_BLIND.identifier).toInt()
val bbSessionCount =
computables.sum(ComputableResult.Field.HAS_BIG_BLIND.identifier).toInt()
results.addStat(BB_SESSION_COUNT, bbSessionCount.toDouble())
val winningSessionCount = computables.sum(ComputableResult.Field.IS_POSITIVE.identifier).toInt()
val winningSessionCount =
computables.sum(ComputableResult.Field.IS_POSITIVE.identifier).toInt()
results.addStat(WINNING_SESSION_COUNT, winningSessionCount.toDouble())
val totalBuyin = computables.sum(ComputableResult.Field.RATED_BUYIN.identifier).toDouble()
val totalBuyin =
computables.sum(ComputableResult.Field.RATED_BUYIN.identifier).toDouble()
results.addStat(TOTAL_BUYIN, totalBuyin)
val totalTips = computables.sum(ComputableResult.Field.RATED_TIPS.identifier).toDouble()
@ -285,12 +317,14 @@ class Calculator {
// Timber.d("########## totalBuyin = ${totalBuyin} ### sum = ${sum}")
val maxNetResult = computables.max(ComputableResult.Field.RATED_NET.identifier)?.toDouble()
val maxNetResult =
computables.max(ComputableResult.Field.RATED_NET.identifier)?.toDouble()
maxNetResult?.let {
results.addStat(MAXIMUM_NET_RESULT, it)
}
val minNetResult = computables.min(ComputableResult.Field.RATED_NET.identifier)?.toDouble()
val minNetResult =
computables.min(ComputableResult.Field.RATED_NET.identifier)?.toDouble()
minNetResult?.let {
results.addStat(MINIMUM_NET_RESULT, it)
}
@ -310,7 +344,10 @@ class Calculator {
// }
if (options.computeLocationsPlayed) {
results.addStat(LOCATIONS_PLAYED, computables.distinctBy { it.session?.location?.id }.size.toDouble())
results.addStat(
LOCATIONS_PLAYED,
computables.distinctBy { it.session?.location?.id }.size.toDouble()
)
}
var average = 0.0 // also used for standard deviation later
@ -334,7 +371,6 @@ class Calculator {
averageBB = bbSum / bbSessionCount
results.addStat(AVERAGE_NET_BB, averageBB)
}
val d2 = Date()
val shouldIterateOverComputables =
(options.progressValues == Options.ProgressValues.STANDARD || options.computeLongestStreak)
@ -381,11 +417,20 @@ class Calculator {
}
val session =
computable.session ?: throw PAIllegalStateException("Computing lone ComputableResult")
computable.session
?: throw PAIllegalStateException("Computing lone ComputableResult")
results.addEvolutionValue(tSum, stat = NET_RESULT, data = session)
results.addEvolutionValue(tSum / index, stat = AVERAGE, data = session)
results.addEvolutionValue(index.toDouble(), stat = NUMBER_OF_GAMES, data = session)
results.addEvolutionValue(tBBSum / tBBSessionCount, stat = AVERAGE_NET_BB, data = session)
results.addEvolutionValue(
index.toDouble(),
stat = NUMBER_OF_GAMES,
data = session
)
results.addEvolutionValue(
tBBSum / tBBSessionCount,
stat = AVERAGE_NET_BB,
data = session
)
results.addEvolutionValue(
(tWinningSessionCount.toDouble() / index.toDouble()),
stat = WIN_RATIO,
@ -394,12 +439,25 @@ class Calculator {
results.addEvolutionValue(
tITMCount.toDouble() / index.toDouble(),
stat = TOURNAMENT_ITM_RATIO,
data = session)
results.addEvolutionValue(tBuyinSum / index, stat = AVERAGE_BUYIN, data = session)
results.addEvolutionValue(computable.ratedNet, stat = STANDARD_DEVIATION, data = session)
data = session
)
results.addEvolutionValue(
tBuyinSum / index,
stat = AVERAGE_BUYIN,
data = session
)
results.addEvolutionValue(
computable.ratedNet,
stat = STANDARD_DEVIATION,
data = session
)
Stat.netBBPer100Hands(tBBSum, tHands)?.let { netBB100 ->
results.addEvolutionValue(netBB100, stat = NET_BB_PER_100_HANDS, data = session)
results.addEvolutionValue(
netBB100,
stat = NET_BB_PER_100_HANDS,
data = session
)
}
Stat.returnOnInvestment(tSum, tBuyinSum)?.let { roi ->
@ -415,32 +473,34 @@ class Calculator {
}
// loseStreak is negative and we want it positive
results.addStat(LONGEST_STREAKS, longestWinStreak.toDouble(), -longestLoseStreak.toDouble())
results.addStat(
LONGEST_STREAKS,
longestWinStreak.toDouble(),
-longestLoseStreak.toDouble()
)
}
val d3 = Date()
val sessionSets = computableGroup.sessionSets(realm, options.shouldSortValues)
results.addStat(NUMBER_OF_SETS, sessionSets.size.toDouble())
var gHourlyDuration: Double? = null
var gBBSum: Double? = null
// var gHourlyDuration: Double? = null
//// var gBBSum: Double? = null
var maxDuration: Double? = null
if (computableGroup.conditions.isEmpty()) { // SessionSets are fine
gHourlyDuration =
sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours)
gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
// gHourlyDuration =
// sessionSets.sum(SessionSet.Field.NET_DURATION.identifier).toDouble() / 3600000 // (milliseconds to hours)
// gBBSum = sessionSets.sum(SessionSet.Field.BB_NET.identifier).toDouble()
sessionSets.max(SessionSet.Field.NET_DURATION.identifier)?.let {
maxDuration = it.toDouble() / 3600000
}
}
val shouldIterateOverSets = computableGroup.conditions.isNotEmpty()
|| options.progressValues != Options.ProgressValues.NONE
val shouldIterateOverSets = options.progressValues != Options.ProgressValues.NONE
|| options.computeDaysPlayed
// || computableGroup.conditions.isNotEmpty()
// Session Set
if (shouldIterateOverSets) {
@ -464,7 +524,7 @@ class Calculator {
tBBSum += setStats.bbSum
tHourlyDuration += setStats.hourlyDuration
tTotalHands += setStats.estimatedHands
tMaxDuration = max(tMaxDuration, setStats.hourlyDuration)
tMaxDuration = max(tMaxDuration, setStats.hourlyDuration.toDouble())
tHourlyRate = tRatedNetSum / tHourlyDuration
tHourlyRateBB = tBBSum / tHourlyDuration
@ -472,8 +532,16 @@ class Calculator {
when (options.progressValues) {
Options.ProgressValues.STANDARD -> {
results.addEvolutionValue(tHourlyRate, stat = HOURLY_RATE, data = sessionSet)
results.addEvolutionValue(tIndex.toDouble(), stat = NUMBER_OF_SETS, data = sessionSet)
results.addEvolutionValue(
tHourlyRate,
stat = HOURLY_RATE,
data = sessionSet
)
results.addEvolutionValue(
tIndex.toDouble(),
stat = NUMBER_OF_SETS,
data = sessionSet
)
results.addEvolutionValue(
sessionSet.hourlyDuration,
tHourlyDuration,
@ -485,12 +553,26 @@ class Calculator {
stat = AVERAGE_HOURLY_DURATION,
data = sessionSet
)
results.addEvolutionValue(tHourlyRateBB, stat = HOURLY_RATE_BB, data = sessionSet)
results.addEvolutionValue(
tHourlyRateBB,
stat = HOURLY_RATE_BB,
data = sessionSet
)
}
Options.ProgressValues.TIMED -> {
results.addEvolutionValue(tRatedNetSum, tHourlyDuration, NET_RESULT, sessionSet)
results.addEvolutionValue(tHourlyRate, tHourlyDuration, HOURLY_RATE, sessionSet)
results.addEvolutionValue(
tRatedNetSum,
tHourlyDuration,
NET_RESULT,
sessionSet
)
results.addEvolutionValue(
tHourlyRate,
tHourlyDuration,
HOURLY_RATE,
sessionSet
)
results.addEvolutionValue(
tIndex.toDouble(),
tHourlyDuration,
@ -509,7 +591,12 @@ class Calculator {
AVERAGE_HOURLY_DURATION,
sessionSet
)
results.addEvolutionValue(tHourlyRateBB, tHourlyDuration, HOURLY_RATE_BB, sessionSet)
results.addEvolutionValue(
tHourlyRateBB,
tHourlyDuration,
HOURLY_RATE_BB,
sessionSet
)
Stat.netBBPer100Hands(tBBSum, tTotalHands)?.let { netBB100 ->
results.addEvolutionValue(
@ -529,32 +616,44 @@ class Calculator {
}
gHourlyDuration = tHourlyDuration
gBBSum = tBBSum
// gHourlyDuration = tHourlyDuration
// gBBSum = tBBSum
maxDuration = tMaxDuration
}
val d4 = Date()
// var hourlyRate = 0.0
// if (gHourlyDuration != null) {
//
// hourlyRate = ratedNet / gHourlyDuration
// if (sessionSets.size > 0) {
// val avgDuration = gHourlyDuration / sessionSets.size
// results.addStat(HOURLY_RATE, hourlyRate)
// results.addStat(AVERAGE_HOURLY_DURATION, avgDuration)
// }
// results.addStat(HOURLY_DURATION, gHourlyDuration)
// }
val timeIntervals = computableGroup.timeIntervals(realm)
val duration = timeIntervals.sum("duration").toDouble()
val breakDuration = sessionSets.sum("breakDuration").toDouble()
var hourlyRate = 0.0
if (gHourlyDuration != null) {
val netHourlyDuration = (duration - breakDuration) / 3600000
val hourlyRate = ratedNet / netHourlyDuration
hourlyRate = ratedNet / gHourlyDuration
if (sessionSets.size > 0) {
val avgDuration = gHourlyDuration / sessionSets.size
results.addStat(HOURLY_RATE, hourlyRate)
results.addStat(AVERAGE_HOURLY_DURATION, avgDuration)
}
results.addStat(HOURLY_DURATION, gHourlyDuration)
}
results.addStat(AVERAGE_HOURLY_DURATION, netHourlyDuration / sessionSets.size)
results.addStat(HOURLY_DURATION, netHourlyDuration)
if (gBBSum != null) {
if (gHourlyDuration != null) {
results.addStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration)
}
results.addStat(AVERAGE_NET_BB, gBBSum / bbSessionCount)
}
results.addStat(HOURLY_RATE_BB, bbSum / netHourlyDuration)
results.addStat(AVERAGE_NET_BB, bbSum / bbSessionCount)
// if (gBBSum != null) {
// if (gHourlyDuration != null) {
// results.addStat(HOURLY_RATE_BB, gBBSum / gHourlyDuration)
// }
// results.addStat(AVERAGE_NET_BB, gBBSum / bbSessionCount)
// }
maxDuration?.let { maxd ->
results.addStat(MAXIMUM_DURATION, maxd) // (milliseconds to hours)
@ -568,45 +667,31 @@ class Calculator {
// Session
var stdSum = 0.0
var stdBBSum = 0.0
var stdBBper100HandsSum = 0.0
var stdBBPer100HandsSum = 0.0
for (computable in computables) {
stdSum += (computable.ratedNet - average).pow(2.0)
stdBBSum += (computable.bbNet - averageBB).pow(2.0)
stdBBper100HandsSum += (computable.bbPer100Hands - bbPer100Hands).pow(2.0)
stdBBPer100HandsSum += (computable.bbPer100Hands - bbPer100Hands).pow(2.0)
}
val standardDeviation = sqrt(stdSum / computables.size)
val standardDeviationBB = sqrt(stdBBSum / computables.size)
val standardDeviationBBper100Hands = sqrt(stdBBper100HandsSum / computables.size)
val standardDeviationBBper100Hands = sqrt(stdBBPer100HandsSum / computables.size)
results.addStat(STANDARD_DEVIATION, standardDeviation)
results.addStat(STANDARD_DEVIATION_BB, standardDeviationBB)
results.addStat(STANDARD_DEVIATION_BB_PER_100_HANDS, standardDeviationBBper100Hands)
// Session Set
if (gHourlyDuration != null) {
var hourlyStdSum = 0.0
for (set in sessionSets) {
val ssStats = SSStats(set, computableGroup.query)
val sHourlyRate = ssStats.hourlyRate
hourlyStdSum += (sHourlyRate - hourlyRate).pow(2.0)
val setHourlyRate = ssStats.hourlyRate
hourlyStdSum += (setHourlyRate - hourlyRate).pow(2.0)
}
val hourlyStandardDeviation = sqrt(hourlyStdSum / sessionSets.size)
results.addStat(STANDARD_DEVIATION_HOURLY, hourlyStandardDeviation)
}
}
val d5 = Date()
val s1 = d2.time - d1.time
val s2 = d3.time - d2.time
val s3 = d4.time - d3.time
val s4 = d5.time - d4.time
Timber.d("Section 1 = $s1")
Timber.d("Section 2 = $s2")
Timber.d("Section 3 = $s3")
Timber.d("Section 4 = $s4")
return results
}
@ -616,14 +701,18 @@ class Calculator {
class SSStats(sessionSet: SessionSet, query: Query) { // Session Set Stats
var hourlyDuration: Double = 0.0
var hourlyDuration: Long = 0L
var estimatedHands: Double = 0.0
var bbSum: Double = 0.0
var ratedNet: Double = 0.0
val hourlyRate: Double
get() {
return this.ratedNet / this.hourlyDuration
return if (this.hourlyDuration > 0L) {
this.ratedNet / this.hourlyDuration.toDouble()
} else {
0.0
}
}
init {
@ -636,10 +725,15 @@ class SSStats(sessionSet: SessionSet, query: Query) { // Session Set Stats
if (setSessions.size == filteredSessions.size) {
this.initStatsWithSet(sessionSet)
} else {
ratedNet = filteredSessions.sumOf { it.computableResult?.ratedNet ?: 0.0 }
bbSum = filteredSessions.sumOf { it.bbNet }
hourlyDuration = filteredSessions.hourlyDuration
estimatedHands = filteredSessions.sumOf { it.estimatedHands }
val intervals = SessionInterval.intervalMap(filteredSessions.toSet())
val netDuration = intervals.sumOf { it.duration }
val estimatedBreak = if (netDuration > 0.0) sessionSet.breakDuration * sessionSet.netDuration / netDuration else 0L
hourlyDuration = netDuration - estimatedBreak
}
}
}
@ -647,7 +741,7 @@ class SSStats(sessionSet: SessionSet, query: Query) { // Session Set Stats
private fun initStatsWithSet(sessionSet: SessionSet) {
ratedNet = sessionSet.ratedNet
bbSum = sessionSet.bbNet
hourlyDuration = sessionSet.hourlyDuration
hourlyDuration = sessionSet.hourlyDuration.toLong()
estimatedHands = sessionSet.estimatedHands
}

@ -64,6 +64,11 @@ class ComputableGroup(val query: Query, var displayedStats: List<Stat>? = null)
return computables
}
fun timeIntervals(realm: Realm): RealmResults<FlatTimeInterval> {
return Filter.queryOn(realm, this.query)
}
/**
* The list of sets to compute
*/

@ -55,18 +55,18 @@ class ReportWhistleBlower(var context: Context) {
val realm = Realm.getDefaultInstance()
this.sessions = realm.where(Session::class.java).findAll()
this.sessions?.addChangeListener { _ ->
sessions = realm.where(Session::class.java).findAll()
sessions?.addChangeListener { _ ->
requestReportLaunch()
}
this.results = realm.where(Result::class.java).findAll()
this.results?.addChangeListener { _ ->
results = realm.where(Result::class.java).findAll()
results?.addChangeListener { _ ->
requestReportLaunch()
}
this.sessionSets = realm.where(SessionSet::class.java).findAll()
this.sessionSets?.addChangeListener { _ ->
sessionSets = realm.where(SessionSet::class.java).findAll()
sessionSets?.addChangeListener { _ ->
requestReportLaunch()
}
@ -156,13 +156,13 @@ class ReportTask(private var whistleBlower: ReportWhistleBlower, var context: Co
}
fun cancel() {
Timber.d("Reportwhistleblower task CANCEL")
// Timber.d("Reportwhistleblower task CANCEL")
this.cancelled = true
}
private fun launchReports() {
Timber.d("====== Report whistleblower launch batch...")
// Timber.d("====== Report whistleblower launch batch...")
CoroutineScope(Dispatchers.Default).launch {

@ -133,7 +133,7 @@ val AbstractList<Session>.hourlyDuration: Double
return intervals.sumOf { it.hourlyDuration }
}
class TimeInterval(var start: Date, var end: Date, var breakDuration: Long) {
class TimeInterval(var start: Date, var end: Date, var breakDuration: Long = 0L) {
val hourlyDuration: Double
get() {

@ -32,9 +32,9 @@ import net.pokeranalytics.android.util.CrashLogging
*
*/
class UnmanagedFilterField(message: String) : Exception(message) {
}
//class UnmanagedFilterField(message: String) : Exception(message) {
//
//}
/**
* Companion-level Interface to indicate an RealmObject class can be filtered and to provide all the fieldNames (eg: parameter's path) needed to be query on.
@ -64,6 +64,7 @@ class FilterHelper {
SessionSet::class.java -> SessionSet.fieldNameForQueryType(queryCondition)
Transaction::class.java -> Transaction.fieldNameForQueryType(queryCondition)
Result::class.java -> Result.fieldNameForQueryType(queryCondition)
FlatTimeInterval::class.java -> FlatTimeInterval.fieldNameForQueryType(queryCondition)
else -> {
CrashLogging.logException(PAIllegalStateException("Filterable type fields are not defined for condition ${queryCondition::class}, class ${T::class}"))
null

@ -3,6 +3,7 @@ package net.pokeranalytics.android.model.migrations
import io.realm.DynamicRealm
import io.realm.RealmMigration
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.realm.FlatTimeInterval
import timber.log.Timber
import java.util.*
@ -341,6 +342,18 @@ class PokerAnalyticsMigration : RealmMigration {
crs.addField("id", String::class.java).setRequired("id", true)
crs.addPrimaryKey("id")
}
schema.create("FlatTimeInterval")?.let { fs ->
fs.addField("id", String::class.java).setRequired("id", true)
fs.addPrimaryKey("id")
fs.addField("startDate", Date::class.java).setRequired("startDate", true)
fs.addField("endDate", Date::class.java).setRequired("endDate", true)
fs.addField("duration", Long::class.java)
schema.get("Session")?.let { ss ->
ss.addRealmSetField("flatTimeIntervals", fs)
}
}
currentVersion++
}

@ -0,0 +1,61 @@
package net.pokeranalytics.android.model.realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
import io.realm.annotations.RealmClass
import net.pokeranalytics.android.model.filter.Filterable
import net.pokeranalytics.android.model.filter.QueryCondition
import java.util.*
@RealmClass
open class FlatTimeInterval : RealmObject(), Filterable {
@PrimaryKey
var id = UUID.randomUUID().toString()
/**
* The start date of the session
*/
var startDate: Date = Date()
set(value) {
field = value
this.computeDuration()
}
/**
* The start date of the session
*/
var endDate: Date = Date()
set(value) {
field = value
this.computeDuration()
}
/**
* the net duration of the session, automatically calculated
*/
var duration: Long = 0L
@LinkingObjects("flatTimeIntervals")
val sessions: RealmResults<Session>? = null
private fun computeDuration() {
duration = endDate.time - startDate.time
}
companion object {
fun fieldNameForQueryType(queryCondition: Class <out QueryCondition>): String? {
Session.fieldNameForQueryType(queryCondition)?.let {
return "sessions.$it"
}
return null
}
}
}

@ -33,10 +33,7 @@ import net.pokeranalytics.android.ui.graph.Graph
import net.pokeranalytics.android.ui.view.*
import net.pokeranalytics.android.ui.view.rows.SessionPropertiesRow
import net.pokeranalytics.android.util.*
import net.pokeranalytics.android.util.extensions.hourMinute
import net.pokeranalytics.android.util.extensions.shortDateTime
import net.pokeranalytics.android.util.extensions.toCurrency
import net.pokeranalytics.android.util.extensions.toMinutes
import net.pokeranalytics.android.util.extensions.*
import java.text.DateFormat
import java.text.NumberFormat
import java.text.ParseException
@ -202,6 +199,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
*/
var startDate: Date? = null
set(value) {
val previous = this.startDate
field = value
if (value == null) {
startDateHourMinuteComponent = null
@ -217,7 +215,9 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
if (value != null && this.endDate != null && value.after(this.endDate)) {
this.endDate = null
}
this.dateChanged()
SessionSetManager.startChanged(this, min(previous, value))
// this.computeStats()
}
@ -227,6 +227,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
@Index
var endDate: Date? = null
set(value) {
val previous = this.endDate
field = value
if (value == null) {
endDateHourMinuteComponent = null
@ -237,7 +238,7 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
}
this.computeNetDuration()
this.dateChanged()
SessionSetManager.endChanged(this, max(previous, value))
this.defineDefaultTournamentBuyinIfNecessary()
// this.computeStats()
}
@ -373,6 +374,9 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
// The custom fields values
var customFieldEntries: RealmList<CustomFieldEntry> = RealmList()
// The list of opponents who participated to the session
var flatTimeIntervals: RealmList<FlatTimeInterval> = RealmList()
// The number of hands played during the sessions
var handsCount: Int? = null
@ -380,10 +384,6 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
this.generateStakes()
}
private fun dateChanged() {
SessionSetManager.sessionDateChanged(this)
}
// /**
// * Manages impacts on SessionSets
// * Should be called when the start / end date are changed
@ -697,10 +697,8 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
CrashLogging.log("Deletes session. Id = ${this.id}")
if (isValid) {
// realm.executeTransaction {
cleanup()
deleteFromRealm()
// }
} else {
CrashLogging.log("Attempt to delete an invalid session")
}
@ -715,6 +713,9 @@ open class Session : RealmObject(), Savable, RowUpdatable, RowRepresentable, Tim
this.sessionSet?.let {
SessionSetManager.removeFromTimeline(this)
}
SessionSetManager.sessionDateChanged(this)
// cleanup unnecessary related objects
this.result?.deleteFromRealm()
this.computableResult?.deleteFromRealm()

@ -1,13 +1,18 @@
package net.pokeranalytics.android.model.utils
import io.realm.Realm
import io.realm.RealmModel
import io.realm.RealmQuery
import io.realm.RealmResults
import net.pokeranalytics.android.exceptions.ModelException
import net.pokeranalytics.android.model.realm.FlatTimeInterval
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.util.extensions.findById
import net.pokeranalytics.android.util.extensions.max
import net.pokeranalytics.android.util.extensions.min
import timber.log.Timber
import java.util.*
class CorruptSessionSetException(message: String) : Exception(message)
@ -17,12 +22,25 @@ class CorruptSessionSetException(message: String) : Exception(message)
*/
object SessionSetManager {
var sessions: RealmResults<Session>
var sessions: RealmResults<Session>? = null
private val sessionIdsToProcess = mutableSetOf<String>()
private var start: Date? = null
private var end: Date? = null
fun configure() {} // launch init
fun startChanged(session: Session, date: Date?) {
this.start = min(this.start, date)
this.sessionIdsToProcess.add(session.id)
}
fun endChanged(session: Session, date: Date?) {
this.end = max(this.end, date)
this.sessionIdsToProcess.add(session.id)
}
fun sessionDateChanged(session: Session) {
this.sessionIdsToProcess.add(session.id)
}
@ -31,11 +49,12 @@ object SessionSetManager {
val realm = Realm.getDefaultInstance()
this.sessions = realm.where(Session::class.java).findAllAsync()
this.sessions.addChangeListener { _, _ ->
if (this.sessionIdsToProcess.isNotEmpty()) {
sessions = realm.where(Session::class.java).findAllAsync()
sessions?.addChangeListener { _, _ ->
if (this.start != null && this.end != null) {
realm.executeTransactionAsync { asyncRealm ->
processSessions(asyncRealm)
cleanUp()
}
}
}
@ -43,21 +62,38 @@ object SessionSetManager {
realm.close()
}
private fun cleanUp() {
this.start = null
this.end = null
// this.sessionIdsToProcess.clear()
}
private fun processSessions(realm: Realm) {
Timber.d("***** processSessions, process count = ${sessionIdsToProcess.size}")
// Timber.d("***** processSessions, process count = ${sessionIdsToProcess.size}")
for (sessionId in this.sessionIdsToProcess) {
realm.findById<Session>(sessionId)?.let { session ->
if (session.startDate != null && session.endDate != null) {
val start = this.start
val end = this.end
val sessions = sessionIdsToProcess.mapNotNull { realm.findById<Session>(it) }
for (session in sessions) {
// Session Sets
val startDate = session.startDate
val endDate = session.endDate
if (startDate != null && endDate != null) {
updateTimeline(session)
} else if (session.sessionSet != null) {
removeFromTimeline(session)
}
}
// FlatTimeIntervals
if (start != null && end != null) {
processFlatTimeInterval(realm, start, end)
}
this.sessionIdsToProcess.clear()
}
/**
@ -76,25 +112,37 @@ object SessionSetManager {
throw ModelException("End date should never be null here")
}
val sessionSets = this.matchingSets(session)
cleanupSessionSets(session, sessionSets)
val start = session.startDate!!
val end = session.endDate!!
// val sessionId = session.id
// realm.executeTransactionAsync { asyncRealm ->
// asyncRealm.findById<Session>(sessionId)?.let { s ->
// val sessionSets = this.matchingSets(session)
// cleanupSessionSets(session, sessionSets)
// }
// }
val sessionSets = this.matchingData<SessionSet>(session.realm, start, end)
cleanupSessionSets(session, sessionSets)
}
private fun matchingSets(session: Session): RealmResults<SessionSet> {
val realm = session.realm
val endDate = session.endDate!! // tested above
val startDate = session.startDate!!
// private fun matchingSets(session: Session): RealmResults<SessionSet> {
// val realm = session.realm
// val endDate = session.endDate!! // tested above
// val startDate = session.startDate!!
//
// val query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java)
//
// query
// .lessThanOrEqualTo("startDate", startDate)
// .greaterThanOrEqualTo("endDate", startDate)
// .or()
// .lessThanOrEqualTo("startDate", endDate)
// .greaterThanOrEqualTo("endDate", endDate)
// .or()
// .greaterThanOrEqualTo("startDate", startDate)
// .lessThanOrEqualTo("endDate", endDate)
//
// return query.findAll()
// }
val query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java)
private inline fun <reified T : RealmModel> matchingData(realm: Realm, startDate: Date, endDate: Date): RealmResults<T> {
val query: RealmQuery<T> = realm.where(T::class.java)
query
.lessThanOrEqualTo("startDate", startDate)
@ -127,7 +175,7 @@ object SessionSetManager {
sessionSets.deleteAllFromRealm()
allImpactedSessions.forEach { impactedSession ->
val sets = matchingSets(impactedSession)
val sets = matchingData<SessionSet>(impactedSession.realm, impactedSession.startDate!!, impactedSession.endDate!!)
this.updateTimeFrames(sets, impactedSession)
}
@ -249,4 +297,155 @@ object SessionSetManager {
}
}
private fun processFlatTimeInterval(realm: Realm, start: Date, end: Date) {
val sessions = matchingData<Session>(realm, start, end)
val intervalsStore = IntervalsStore(sessions.toSet())
intervalsStore.intervals.forEach { it.deleteFromRealm() }
val intervals = SessionInterval.intervalMap(intervalsStore.sessions)
for (interval in intervals) {
val sortedDates = interval.dates.sorted()
for (i in (0 until sortedDates.size - 1)) {
val s = sortedDates[i]
val e = sortedDates[i + 1]
val matchingSessions = interval.sessions.filter {
val sd = it.startDate
val ed = it.endDate
(sd != null && ed != null && sd <= s && ed >= e)
}
if (matchingSessions.isNotEmpty()) {
Timber.d("**** Create FTI: $s - $e")
val fti = FlatTimeInterval()
fti.startDate = s
fti.endDate = e
matchingSessions.forEach { it.flatTimeIntervals.add(fti) }
realm.insertOrUpdate(fti)
} else {
Timber.w("The FTI has no sessions")
}
}
}
}
}
class IntervalsStore(sessions: Set<Session>) {
var start: Date = Date()
var end: Date = Date(0L)
val intervals = mutableSetOf<FlatTimeInterval>()
val sessions = mutableSetOf<Session>()
private val sessionIds: MutableSet<String> = mutableSetOf()
init {
processSessions(sessions)
}
private fun processSessions(sessions: Set<Session>) {
this.sessions.addAll(sessions)
for (session in sessions) {
loadIntervals(session)
}
}
private fun loadIntervals(session: Session) {
if (sessionIds.contains(session.id)) {
return
}
session.startDate?.let { this.start = min(this.start, it) }
session.endDate?.let { this.end = max(this.end, it) }
this.sessionIds.add(session.id)
for (fti in session.flatTimeIntervals) {
this.intervals.add(fti)
fti.sessions?.let { sessions ->
for (s in sessions) {
loadIntervals(s)
}
}
}
}
}
class SessionInterval(session: Session) {
var start: Date
var end: Date?
var sessions: MutableSet<Session> = mutableSetOf()
val dates: MutableSet<Date> = mutableSetOf()
val duration: Long
get() {
val endDate = end ?: Date()
return endDate.time - start.time
}
init {
this.start = session.startDate!!
this.end = session.endDate
this.addSession(session)
}
private fun addSession(session: Session) {
this.sessions.add(session)
session.startDate?.let { this.dates.add(it) }
session.endDate?.let { endDate ->
this.dates.add(endDate)
if (endDate > end) {
end = endDate
}
}
}
companion object {
fun intervalMap(sessions: Set<Session>): List<SessionInterval> {
val sorted = sessions.sortedBy { it.startDate }
val intervals = mutableListOf<SessionInterval>()
sorted.firstOrNull()?.let { firstSession ->
var currentInterval = SessionInterval(firstSession)
intervals.add(currentInterval)
for (session in sessions.drop(1)) {
val start = session.startDate!!
val currentEnd = currentInterval.end
if (currentEnd != null && start > currentEnd) {
val interval = SessionInterval(session)
currentInterval = interval
intervals.add(interval)
} else {
currentInterval.addSession(session)
}
}
}
intervals.forEach {
Timber.d("s = ${it.start}, e = ${it.end}")
}
return intervals
}
}
}

@ -167,7 +167,7 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
val async = async {
val s = Date()
Timber.d(">>> start...")
// Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
realm.refresh()
@ -199,7 +199,6 @@ class StatisticsFragment : FilterableFragment(), RealmAsyncListener {
Timber.d(">>> Launch statistics computations")
val filter: Filter? = this.currentFilter(this.requireContext(), realm)?.let {
if (it.filterableType == currentFilterable) { it } else { null }
}

@ -27,6 +27,7 @@ import net.pokeranalytics.android.ui.view.rows.CustomizableRowRepresentable
import net.pokeranalytics.android.ui.view.rows.StatRow
import net.pokeranalytics.android.util.NULL_TEXT
import net.pokeranalytics.android.util.TextFormat
import timber.log.Timber
open class ComposableTableReportFragment : RealmFragment(), StaticRowRepresentableDataSource,
RowRepresentableDelegate {

@ -388,7 +388,7 @@ class CalendarFragment : RealmFragment(), StaticRowRepresentableDataSource,
val async = async {
val s = Date()
Timber.d(">>> start...")
// Timber.d(">>> start...")
val realm = Realm.getDefaultInstance()
realm.refresh()
@ -436,6 +436,8 @@ class CalendarFragment : RealmFragment(), StaticRowRepresentableDataSource,
private fun launchStatComputation(realm: Realm) {
return
Timber.d(">>> Launch calendar computations")
val calendar = Calendar.getInstance()
@ -660,8 +662,8 @@ class CalendarFragment : RealmFragment(), StaticRowRepresentableDataSource,
}
}
Timber.d("Display data: ${System.currentTimeMillis() - startDate.time}ms")
Timber.d("Rows: ${rows.size}")
// Timber.d("Display data: ${System.currentTimeMillis() - startDate.time}ms")
// Timber.d("Rows: ${rows.size}")
this.calendarAdapter.notifyDataSetChanged()

@ -19,9 +19,7 @@ import net.pokeranalytics.android.api.BlogPostApi
import net.pokeranalytics.android.databinding.FragmentFeedBinding
import net.pokeranalytics.android.exceptions.PAIllegalStateException
import net.pokeranalytics.android.model.LiveData
import net.pokeranalytics.android.model.realm.Filter
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.Transaction
import net.pokeranalytics.android.model.realm.*
import net.pokeranalytics.android.model.realm.handhistory.HandHistory
import net.pokeranalytics.android.ui.activity.BillingActivity
import net.pokeranalytics.android.ui.activity.components.RequestCode
@ -87,10 +85,10 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
override fun asyncListenedEntityChange(realm: Realm, clazz: Class<out RealmModel>) {
Timber.d("asyncListenedEntityChange for $clazz")
// Timber.d("asyncListenedEntityChange for $clazz")
when (clazz.kotlin) {
Session::class -> {
Timber.d("WOWOWOOWOOWOWOWOWOWOWOWOWO")
// Timber.d("WOWOWOOWOOWOWOWOWOWOWOWOWO")
this.sessionAdapter.refreshData()
this.sessionAdapter.notifyDataSetChanged()
}
@ -286,9 +284,20 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
displayBlogPostButton()
binding.postButton.setOnClickListener {
Preferences.setBlogTipsTapped(requireContext())
parentActivity?.openUrl(URL.BLOG_TIPS.value)
displayBlogPostButton()
getRealm().executeTransactionAsync { realm ->
realm.where<Session>().findAll().deleteAllFromRealm()
realm.where<SessionSet>().findAll().deleteAllFromRealm()
realm.where<FlatTimeInterval>().findAll().deleteAllFromRealm()
realm.where<Result>().findAll().deleteAllFromRealm()
realm.where<ComputableResult>().findAll().deleteAllFromRealm()
}
// Preferences.setBlogTipsTapped(requireContext())
// parentActivity?.openUrl(URL.BLOG_TIPS.value)
// displayBlogPostButton()
}
binding.postButton.viewTreeObserver.addOnGlobalLayoutListener {
@ -632,7 +641,7 @@ class FeedFragment : FilterableFragment(), RowRepresentableDelegate, PurchaseLis
show = true
this.badgeDrawable?.number = newCount
}
this.binding.postButton.isVisible = show
this.binding.postButton.isVisible = true
this.badgeDrawable?.isVisible = show
}

@ -7,6 +7,32 @@ import java.util.*
// Calendar
fun min(d1: Date, d2: Date): Date {
return if (d1 < d2) d1 else d2
}
fun max(d1: Date, d2: Date): Date {
return if (d1 > d2) d1 else d2
}
@JvmName("min1")
fun min(d1: Date?, d2: Date?): Date? {
return if (d1 != null) {
if (d2 != null) min(d1, d2) else d1
} else {
d2
}
}
@JvmName("max1")
fun max(d1: Date?, d2: Date?): Date? {
return if (d1 != null) {
if (d2 != null) max(d1, d2) else d1
} else {
d2
}
}
// Return a double representing the hour / minute of a date from a calendar
fun Calendar.hourMinute(): Double {
return (this.get(Calendar.HOUR_OF_DAY) + this.get(Calendar.MINUTE).toDouble() / 60.0).roundOffDecimal()

Loading…
Cancel
Save