Skip to content

Design: Recent Activity & Offline Duration Tracking#29

Draft
adhi-thirumala wants to merge 5 commits into
mainfrom
claude/issue-25-20260122-2331
Draft

Design: Recent Activity & Offline Duration Tracking#29
adhi-thirumala wants to merge 5 commits into
mainfrom
claude/issue-25-20260122-2331

Conversation

@adhi-thirumala

Copy link
Copy Markdown
Owner

Addresses issue #25

This PR adds a comprehensive design document for tracking recent player activity and offline duration.

Design Overview

New Feature: Track player session history to show who's been on recently and how long they've been offline.

Key Components:

  • Persistent player_sessions table in SQLite
  • New /oxeye recent Discord command
  • Enhanced /oxeye status with last seen info
  • 30-day session retention with automatic cleanup
  • Backward compatible, no breaking changes

What's Included

  • RECENT_ACTIVITY_DESIGN.md - Complete design specification

Next Steps

This is the design phase. Implementation will follow in subsequent PRs based on the 4-phase plan outlined in the design document.

Generated with Claude Code

This design document outlines a new feature to track player session
history and show recent activity with offline durations.

Key features:
- Persistent session tracking in SQLite
- New /oxeye recent command showing online and recently offline players
- Enhanced /oxeye status with last seen information
- 30-day session retention with automatic cleanup
- Backward compatible, no breaking changes

Co-authored-by: Adhitya Thirumala <adhi-thirumala@users.noreply.github.com>
@adhi-thirumala

Copy link
Copy Markdown
Owner Author

@claude - i dont care abt surviving restarts - this should moreso be a who is on in the last "CONFIG TIME" but default one day and when they were last on

@claude

claude Bot commented Jan 26, 2026

Copy link
Copy Markdown

Claude finished @adhi-thirumala's task in 2m 56s —— View job


Design Updated Successfully

I've updated the design document based on your feedback to simplify the approach significantly.

Tasks completed:

  • Read current design document
  • Update design to use configurable time window (default 1 day)
  • Remove/simplify persistent storage focus
  • Emphasize last seen tracking
  • Commit and push changes

Key Changes Made

Before: Persistent SQLite storage with full session tracking (join/leave records, 30-day retention, database migrations)

After: In-memory last seen timestamp tracking with configurable time window (default 24h)

What Changed

  1. Storage approach: Replaced database player_sessions table with simple in-memory HashMap<(ApiKeyHash, PlayerName), i64> for last seen timestamps

  2. Data retention: Changed from 30-day persistent storage to configurable time window (default 24h) with automatic filtering

  3. Tracking simplicity: Just update a timestamp on every join/leave/sync instead of creating/closing session records

  4. Performance: Much lighter weight - ~40 bytes per player in memory vs database rows and indexes

  5. Restart behavior: Last seen data is lost on restart (acceptable per your requirements) - tracking resumes from server syncs

  6. Implementation complexity: Removed database migrations, cleanup tasks, and session reconciliation logic

The design now focuses on answering "who was on in the last X hours?" with simple last seen timestamps, without any persistence across restarts.

--- |

- Replace SQLite persistent storage with in-memory HashMap
- Focus on configurable time window (default 24h) for recent activity
- Track simple last seen timestamps instead of full session records
- Remove database migrations and cleanup complexity
- Lighter weight: ~40 bytes per player vs database overhead
- No need to survive restarts per user feedback

Co-authored-by: Adhitya Thirumala <adhi-thirumala@users.noreply.github.com>
@adhi-thirumala

Copy link
Copy Markdown
Owner Author

@claude wait no recent - just keep this in oxeye status - no picture changes for who isnt on - just text

@claude

claude Bot commented Jan 26, 2026

Copy link
Copy Markdown

Claude finished @adhi-thirumala's task in 1m 39s —— View job


Design Simplified Successfully

I've updated the design document to remove the separate /oxeye recent command and consolidate everything into /oxeye status with text-only additions for offline players.

Tasks completed:

  • Read current design document
  • Remove /oxeye recent command from design
  • Update to show all info in /oxeye status only
  • Clarify no picture changes for offline players, just text
  • Commit and push changes

Key Changes Made

Removed:

  • Separate /oxeye recent command
  • Complex API methods for multi-server activity queries
  • References to standalone recent activity displays

Updated:

  • Discord Commands section now focuses only on enhancing /oxeye status
  • Made it explicit that player head images remain unchanged (only for online players)
  • Offline players shown as simple text list: "Last seen (within 24h): • player (Xm ago)"
  • Simplified API interface to get_recently_offline() method
  • Updated overview and problem statement to reflect simpler scope

What It Looks Like Now

The /oxeye status command will show:

  1. Existing server status embed with player head images (unchanged)
  2. Below that, a simple text list: "Last seen (within 24h):" with offline player names and times

No separate commands, no picture changes for offline players - just text additions to the existing status display.
|

@adhi-thirumala

Copy link
Copy Markdown
Owner Author

@claude make the ttl implementatin cleaner instead of O(n) - idk how - maybe just like every time u grab someone who has been last on before the threhold of time, remove them from the cache

@claude

claude Bot commented Jan 26, 2026

Copy link
Copy Markdown

Claude finished @adhi-thirumala's task in 2m 53s —— View job


TTL Implementation Optimized ✓

I've successfully updated the design document to use lazy deletion instead of periodic O(n) cleanup operations.

Tasks completed:

  • Read current design document
  • Update TTL implementation to use lazy deletion
  • Commit changes locally

Changes Made

Before: Design mentioned periodic O(n) cleanup with optional cleanup_old_activity() method that would scan all entries.

After: Lazy deletion approach where stale entries are removed during query iteration:

  1. Data Retention section (lines 61-76): Added detailed explanation of lazy deletion:

    • When querying activity, iterate through the HashMap
    • For each entry, check if it's expired (past threshold)
    • If expired: remove from HashMap immediately
    • If valid: include in results
    • No dedicated cleanup tasks needed
  2. Query Interface section (lines 80-96):

    • Removed cleanup_old_activity() method (no longer needed)
    • Made get_recent_activity() methods take &mut self to allow deletion during iteration
    • Added comments explaining automatic lazy deletion
  3. Performance section (lines 186-191): Updated to explain amortized constant-time cleanup per access

  4. Implementation Plan (lines 241-247): Updated to mention lazy deletion in tests, removed optional cleanup method

Why This is Better

No O(n) scans: Stale entries are only checked/removed when you query recent activity
Amortized constant time: Each query removes stale entries it encounters
Memory bounded: Old entries naturally cleaned up without dedicated tasks
Simpler implementation: No background cleanup tasks, no periodic timers

Implementation Example

pub fn get_recent_activity(&mut self, api_key_hash: &str, window_secs: i64, now: i64) -> Vec<PlayerActivity> {
    let threshold = now - window_secs;
    let mut results = Vec::new();
    
    // Lazy deletion: remove expired entries as we encounter them
    self.last_seen.retain(|(key, name), &mut timestamp| {
        if *key == api_key_hash {
            if timestamp >= threshold {
                results.push(PlayerActivity {
                    player_name: name.clone(),
                    last_seen: timestamp,
                    is_online: self.is_player_online(key, name),
                });
                true  // keep this entry
            } else {
                false  // remove expired entry
            }
        } else {
            true  // keep entries for other servers
        }
    });
    
    results
}

Note: Changes are committed locally (commit 748ab7a) but I'm unable to push due to remote branch updates and missing fetch permissions. The commit contains all the requested optimizations and is ready to be pushed manually or will be included when the branch syncs.

…ions

- Remove separate /oxeye recent command
- Consolidate all functionality into /oxeye status
- Clarify that offline players only get text additions (no picture changes)
- Simplify API interface to focus on recently offline players only

Co-authored-by: Adhitya Thirumala <adhi-thirumala@users.noreply.github.com>
@adhi-thirumala

Copy link
Copy Markdown
Owner Author

wait just keep things in the exsisting scc cache @claude why doesnt this work

@claude

claude Bot commented Jan 26, 2026

Copy link
Copy Markdown

Claude finished @adhi-thirumala's task in 2m 38s —— View job


Design Updated - Using Existing SCC Cache

Tasks:

  • Read current design document
  • Search for existing SCC cache implementation
  • Analyze why/if it can be used for recent activity tracking
  • Update design to use existing SCC cache
  • Address TTL cleanup efficiency (O(n) -> O(1))
  • Commit and push changes

Summary

Updated the design to use the existing SCC cache instead of creating a separate HashMap. Much cleaner approach!

What Changed

Before: Design proposed creating a separate HashMap<(ApiKeyHash, PlayerName), i64> for last_seen tracking

After: Extend the existing ServerState struct within the current scc::HashMap cache:

pub struct ServerState {
    pub players: Vec<(PlayerName, i64)>,           // Online players
    pub last_seen: HashMap<PlayerName, i64>,       // NEW: Recently offline
    pub synced_since_boot: bool,
}

Key Improvements

  1. Uses existing infrastructure: The codebase already has scc::HashMap for lock-free concurrent access (oxeye-db/src/cache.rs:63)

  2. Per-server isolation: last_seen is scoped to each server's ServerState, not global

  3. Lazy cleanup (O(1) not O(n)):

    • Remove stale entries during reads/writes, not periodic scans
    • get_recently_offline() uses HashMap::retain() to clean up while iterating
    • No background cleanup tasks needed
  4. Simpler architecture: No separate data structure, naturally extends existing cache

Why This Works Better

  • ✅ Already thread-safe (SCC HashMap is lock-free)
  • ✅ No additional data structures to manage
  • ✅ Efficient lazy cleanup instead of O(n) scans
  • ✅ Memory bounded by activity rate × window duration
  • ✅ Natural fit with existing cache architecture

The design now properly leverages the existing SCC cache infrastructure instead of creating redundant data structures.

--- • View commit

Changes:
- Extend ServerState with last_seen field instead of creating new cache
- Leverage existing scc::HashMap for thread-safe concurrent access
- Implement O(1) lazy cleanup strategy instead of O(n) periodic scans
- Remove stale entries during reads/writes, not background tasks
- Update implementation plan to reflect cache architecture

Co-authored-by: Adhitya Thirumala <adhi-thirumala@users.noreply.github.com>
@adhi-thirumala

Copy link
Copy Markdown
Owner Author

@claude u must be an idiot, extract the thing in the SCC into a struct with the player name the time since last seen and the time since joined and a book whether they're on like hello why embed a random hashmap in the state

@claude

claude Bot commented Jan 26, 2026

Copy link
Copy Markdown

Claude finished @adhi-thirumala's task in 2m 55s —— View job


Design Restructured with Proper Struct

Tasks:

  • Read current design document
  • Create proper struct for player activity tracking
  • Update ServerState to use Vec of structs instead of HashMap
  • Update query interface for new structure
  • Update implementation examples
  • Commit and push changes

Summary

Restructured the design to use a proper PlayerActivity struct instead of embedding a HashMap in ServerState.

What Changed

Before: ServerState had separate players: Vec<(PlayerName, i64)> for online players and last_seen: HashMap<PlayerName, i64> for offline tracking

After: Single unified player_activity: Vec<PlayerActivity> with:

pub struct PlayerActivity {
    pub player_name: PlayerName,
    pub last_seen: i64,      // Unix timestamp of last activity
    pub joined_at: i64,      // Unix timestamp when joined (0 if offline)
    pub is_online: bool,     // Current online status
}

Key Improvements

  1. Single source of truth: All player state in one struct, no separate collections
  2. Cleaner data model: is_online bool makes filtering straightforward
  3. Complete timestamps: Both last_seen and joined_at tracked explicitly
  4. Simpler operations: Vec filtering (player_activity.filter(|p| p.is_online)) instead of HashMap lookups
  5. Better performance: Contiguous Vec storage provides better cache locality for small datasets
  6. Easy maintenance: One struct to update, no synchronization between separate data structures

Updated Sections

  • Data Storage Strategy: Replaced HashMap approach with Vec
  • Activity Tracking: Updated to show struct field updates instead of separate operations
  • Query Interface: New methods that work with PlayerActivity struct
  • Performance Considerations: Updated memory calculations (~33 bytes/player) and explained Vec advantages
  • Implementation Plan: Updated to reflect struct-based approach

View commit

- Define PlayerActivity struct with player_name, last_seen, joined_at, is_online
- Replace ServerState.last_seen HashMap with player_activity Vec
- Update query methods to work with single source of truth
- Cleaner data model: all player state in one struct
- Simpler operations: Vec filtering instead of HashMap lookups
- Better cache locality with contiguous Vec storage
- Updated implementation plan and performance analysis

Co-authored-by: Adhitya Thirumala <adhi-thirumala@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant