Skip to content

Commit

Permalink
Achievements BE part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
megrogan committed Jun 25, 2024
1 parent 62f5666 commit c1be399
Show file tree
Hide file tree
Showing 22 changed files with 266 additions and 28 deletions.
2 changes: 2 additions & 0 deletions backend/canisters/user/api/can.did
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,7 @@ type InitialStateResponse = variant {
suspended : bool;
pin_number_settings : opt PinNumberSettings;
local_user_index_canister_id : CanisterId;
achievements : vec ChitEarned;
};
};

Expand Down Expand Up @@ -911,6 +912,7 @@ type UpdatesResponse = variant {
NoChange;
};
suspended : opt bool;
achievements : vec ChitEarned;
};
SuccessNoUpdates;
};
Expand Down
3 changes: 2 additions & 1 deletion backend/canisters/user/api/src/queries/initial_state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use candid::CandidType;
use serde::{Deserialize, Serialize};
use types::{CanisterId, Chat, ChatId, DirectChatSummary, Empty, GroupChatSummary, TimestampMillis, UserId};
use types::{CanisterId, Chat, ChatId, ChitEarned, DirectChatSummary, Empty, GroupChatSummary, TimestampMillis, UserId};

pub type Args = Empty;

Expand All @@ -21,6 +21,7 @@ pub struct SuccessResult {
pub suspended: bool,
pub pin_number_settings: Option<PinNumberSettings>,
pub local_user_index_canister_id: CanisterId,
pub achievements: Vec<ChitEarned>,
}

#[derive(CandidType, Serialize, Deserialize, Debug)]
Expand Down
5 changes: 4 additions & 1 deletion backend/canisters/user/api/src/queries/updates.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::initial_state::PinNumberSettings;
use candid::CandidType;
use serde::{Deserialize, Serialize};
use types::{Chat, ChatId, CommunityId, DirectChatSummary, DirectChatSummaryUpdates, OptionUpdate, TimestampMillis, UserId};
use types::{
Chat, ChatId, ChitEarned, CommunityId, DirectChatSummary, DirectChatSummaryUpdates, OptionUpdate, TimestampMillis, UserId,
};

#[derive(CandidType, Serialize, Deserialize, Debug)]
pub struct Args {
Expand All @@ -28,6 +30,7 @@ pub struct SuccessResult {
pub blocked_users: Option<Vec<UserId>>,
pub suspended: Option<bool>,
pub pin_number_settings: OptionUpdate<PinNumberSettings>,
pub achievements: Vec<ChitEarned>,
}

#[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
Expand Down
17 changes: 15 additions & 2 deletions backend/canisters/user/impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use std::collections::HashSet;
use std::ops::Deref;
use std::time::Duration;
use types::{
BuildVersion, CanisterId, Chat, ChatId, ChatMetrics, CommunityId, Cryptocurrency, Cycles, Document, Notification,
TimestampMillis, Timestamped, UserId,
Achievement, BuildVersion, CanisterId, Chat, ChatId, ChatMetrics, ChitEarned, ChitEarnedReason, CommunityId,
Cryptocurrency, Cycles, Document, Notification, TimestampMillis, Timestamped, UserId,
};
use user_canister::{NamedAccount, UserCanisterEvent};
use utils::canister_event_sync_queue::CanisterEventSyncQueue;
Expand Down Expand Up @@ -173,6 +173,16 @@ impl RuntimeState {
},
}
}

pub fn insert_achievement(&mut self, achievement: Achievement) {
if self.data.achievements.insert(achievement.clone()) {
self.data.chit_events.push(ChitEarned {
amount: achievement.chit_reward() as i32,
timestamp: self.env.now(),
reason: ChitEarnedReason::Achievement(achievement),
});
}
}
}

#[derive(Serialize, Deserialize)]
Expand Down Expand Up @@ -214,6 +224,8 @@ struct Data {
pub pin_number: PinNumber,
pub btc_address: Option<String>,
pub chit_events: ChitEarnedEvents,
#[serde(default)]
pub achievements: HashSet<Achievement>,
pub rng_seed: [u8; 32],
}

Expand Down Expand Up @@ -272,6 +284,7 @@ impl Data {
pin_number: PinNumber::default(),
btc_address: None,
chit_events: ChitEarnedEvents::default(),
achievements: HashSet::new(),
rng_seed: [0; 32],
}
}
Expand Down
76 changes: 74 additions & 2 deletions backend/canisters/user/impl/src/lifecycle/post_upgrade.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::lifecycle::{init_env, init_state};
use crate::memory::get_upgrades_memory;
use crate::{mutate_state, Data};
use crate::{mutate_state, Data, RuntimeState};
use canister_logger::LogEntry;
use canister_tracing_macros::trace;
use chat_events::Reader;
use ic_cdk::post_upgrade;
use stable_memory::get_reader;
use std::time::Duration;
use tracing::info;
use types::{Empty, Milliseconds};
use types::{Achievement, Empty, Milliseconds, UserId};
use user_canister::post_upgrade::Args;
use utils::time::DAY_IN_MS;

Expand Down Expand Up @@ -37,6 +38,8 @@ fn post_upgrade(args: Args) {
ic_cdk_timers::set_timer(Duration::ZERO, mark_user_canister_empty);
}
});

mutate_state(initialize_achievements);
}

fn mark_user_canister_empty() {
Expand All @@ -49,3 +52,72 @@ fn mark_user_canister_empty() {
);
})
}

fn initialize_achievements(state: &mut RuntimeState) {
let longest_streak = state.data.chit_events.init_streak();

let me: UserId = state.env.canister_id().into();
let now = state.env.now();

if state.data.group_chats.len() > 0 {
state.insert_achievement(Achievement::JoinedGroup);
}

if state.data.communities.len() > 0 {
state.insert_achievement(Achievement::JoinedCommunity);
}

if state.data.direct_chats.iter().any(|c| {
c.events
.main_events_reader()
.iter_latest_messages(None)
.any(|m| m.event.sender == me)
}) {
state.insert_achievement(Achievement::SentDirectMessage);
}

if state.data.direct_chats.iter().any(|c| {
c.events
.main_events_reader()
.iter_latest_messages(None)
.any(|m| m.event.sender == c.them)
}) {
state.insert_achievement(Achievement::ReceivedDirectMessage);
}

if state.data.avatar.value.is_some() {
state.insert_achievement(Achievement::SetAvatar);
}

if !state.data.bio.value.is_empty() {
state.insert_achievement(Achievement::SetBio);
}

if state.data.display_name.value.is_some() {
state.insert_achievement(Achievement::SetDisplayName);
}

if let Some(diamond_expires) = state.data.diamond_membership_expires_at {
state.insert_achievement(Achievement::UpgradedToDiamond);

if (diamond_expires - now) > (5 * 365 * DAY_IN_MS) {
state.insert_achievement(Achievement::UpgradedToGoldDiamond);
}
}

if longest_streak >= 3 {
state.insert_achievement(Achievement::Streak3);
}

if longest_streak >= 7 {
state.insert_achievement(Achievement::Streak7);
}

if longest_streak >= 14 {
state.insert_achievement(Achievement::Streak14);
}

if longest_streak >= 30 {
state.insert_achievement(Achievement::Streak30);
}
}
55 changes: 50 additions & 5 deletions backend/canisters/user/impl/src/model/chit.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
use serde::{Deserialize, Serialize};
use types::{ChitEarned, TimestampMillis};
use std::cmp::max;
use types::{ChitEarned, ChitEarnedReason, TimestampMillis};
use utils::streak::Streak;

#[derive(Serialize, Deserialize, Default)]
pub struct ChitEarnedEvents {
events: Vec<ChitEarned>,
#[serde(default)]
streak: Streak,
}

impl ChitEarnedEvents {
pub fn init_streak(&mut self) -> u16 {
let mut max_streak: u16 = 0;

for event in self.events.iter() {
if matches!(event.reason, ChitEarnedReason::DailyClaim) {
self.streak.claim(event.timestamp);

max_streak = max(max_streak, self.streak.days(event.timestamp))
}
}

max_streak
}

pub fn push(&mut self, event: ChitEarned) {
let mut sort = false;

Expand All @@ -16,6 +34,10 @@ impl ChitEarnedEvents {
}
}

if matches!(event.reason, ChitEarnedReason::DailyClaim) {
self.streak.claim(event.timestamp);
}

self.events.push(event);

if sort {
Expand Down Expand Up @@ -51,11 +73,33 @@ impl ChitEarnedEvents {

(page, self.events.len() as u32)
}

pub fn achievements(&self, since: Option<TimestampMillis>) -> Vec<ChitEarned> {
self.events
.iter()
.rev()
.take_while(|e| since.map_or(true, |ts| e.timestamp > ts))
.filter(|e| matches!(e.reason, ChitEarnedReason::Achievement(_)))
.cloned()
.collect()
}

pub fn has_achievements_since(&self, since: TimestampMillis) -> bool {
self.events
.iter()
.rev()
.take_while(|e| e.timestamp > since)
.any(|e| matches!(e.reason, ChitEarnedReason::Achievement(_)))
}

pub fn streak(&self, now: TimestampMillis) -> u16 {
self.streak.days(now)
}
}

#[cfg(test)]
mod tests {
use types::ChitEarnedReason;
use types::{Achievement, ChitEarnedReason};

use super::*;

Expand Down Expand Up @@ -130,6 +174,7 @@ mod tests {

fn init_test_data() -> ChitEarnedEvents {
ChitEarnedEvents {
streak: Streak::new(),

Check failure on line 177 in backend/canisters/user/impl/src/model/chit.rs

View workflow job for this annotation

GitHub Actions / run clippy

no function or associated item named `new` found for struct `utils::streak::Streak` in the current scope

Check failure on line 177 in backend/canisters/user/impl/src/model/chit.rs

View workflow job for this annotation

GitHub Actions / run unit tests

no function or associated item named `new` found for struct `utils::streak::Streak` in the current scope
events: vec![
ChitEarned {
amount: 200,
Expand All @@ -149,7 +194,7 @@ mod tests {
ChitEarned {
amount: 500,
timestamp: 13,
reason: ChitEarnedReason::Achievement("Bio".to_string()),
reason: ChitEarnedReason::Achievement(Achievement::SetBio),
},
ChitEarned {
amount: 300,
Expand All @@ -159,12 +204,12 @@ mod tests {
ChitEarned {
amount: 500,
timestamp: 15,
reason: ChitEarnedReason::Achievement("Avatar".to_string()),
reason: ChitEarnedReason::Achievement(Achievement::SetAvatar),
},
ChitEarned {
amount: 500,
timestamp: 16,
reason: ChitEarnedReason::Achievement("First message".to_string()),
reason: ChitEarnedReason::Achievement(Achievement::SentDirectMessage),
},
],
}
Expand Down
1 change: 1 addition & 0 deletions backend/canisters/user/impl/src/queries/initial_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@ fn initial_state_impl(state: &RuntimeState) -> Response {
suspended: state.data.suspended.value,
pin_number_settings: state.data.pin_number.enabled().then(|| state.data.pin_number.settings(now)),
local_user_index_canister_id: state.data.local_user_index_canister_id,
achievements: state.data.chit_events.achievements(None),
})
}
6 changes: 5 additions & 1 deletion backend/canisters/user/impl/src/queries/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ fn updates_impl(updates_since: TimestampMillis, state: &RuntimeState) -> Respons
|| state.data.direct_chats.any_updated(updates_since)
|| state.data.group_chats.any_updated(updates_since)
|| state.data.favourite_chats.any_updated(updates_since)
|| state.data.communities.any_updated(updates_since);
|| state.data.communities.any_updated(updates_since)
|| state.data.chit_events.has_achievements_since(updates_since);

// Short circuit prior to calling `ic0.time()` so that caching works effectively
if !has_any_updates {
Expand Down Expand Up @@ -123,6 +124,8 @@ fn updates_impl(updates_since: TimestampMillis, state: &RuntimeState) -> Respons
OptionUpdate::NoChange
};

let achievements = state.data.chit_events.achievements(Some(updates_since));

Success(SuccessResult {
timestamp: now,
username,
Expand All @@ -135,5 +138,6 @@ fn updates_impl(updates_since: TimestampMillis, state: &RuntimeState) -> Respons
blocked_users,
suspended,
pin_number_settings,
achievements,
})
}
Loading

0 comments on commit c1be399

Please sign in to comment.