diff options
Diffstat (limited to 'gcsdk/steamextra')
21 files changed, 5418 insertions, 0 deletions
diff --git a/gcsdk/steamextra/clientenums.h b/gcsdk/steamextra/clientenums.h new file mode 100644 index 0000000..a2888d8 --- /dev/null +++ b/gcsdk/steamextra/clientenums.h @@ -0,0 +1,611 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CLIENTENUMS_H +#define CLIENTENUMS_H +#ifdef _WIN32 +#pragma once +#endif + + +enum ELogonState +{ + k_ELogonStateNotLoggedOn = 0, + k_ELogonStateLoggingOn = 1, + k_ELogonStateLoggingOff = 2, + k_ELogonStateLoggedOn = 3 +}; + + +// Enums for all personal questions supported by the system. +enum EPersonalQuestion +{ + // Never ever change these after initial release. + k_EPSMsgNameOfSchool = 0, // Question: What is the name of your school? + k_EPSMsgFavoriteTeam = 1, // Question: What is your favorite team? + k_EPSMsgMothersName = 2, // Question: What is your mother's maiden name? + k_EPSMsgNameOfPet = 3, // Question: What is the name of your pet? + k_EPSMsgChildhoodHero = 4, // Question: Who was your childhood hero? + k_EPSMsgCityBornIn = 5, // Question: What city were you born in? + + k_EPSMaxPersonalQuestion +}; + + +// account flags (stored in DB) +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASEd +enum EAccountFlags +{ + m_EAccountFlagNormalUser = 0, // Standard user level (yes, this is meant to be zero) + k_EAccountFlagPersonaNameSet = ( 1 << 0 ), // true if the user has set the persona name they really want, instead of the auto-generated one + k_EAccountFlagUnbannable = ( 1 << 1 ), // whatever happens, this account can't be banned + k_EAccountFlagPasswordSet = ( 1 << 2 ), // we've set the password at least once on this account + k_EAccountFlagSupport = ( 1 << 3 ), // Enables use of web support tool + k_EAccountFlagAdmin = ( 1 << 4 ), // The name says it all, can do everything + k_EAccountFlagSupervisor = ( 1 << 5 ), // support supervisory role + k_EAccountFlagAppEditor = ( 1 << 6 ), // Can edit app info + k_EAccountFlagHWIDSet = ( 1 << 7 ), // Set HWID once + k_EAccountFlagPersonalQASet = ( 1 << 8 ), // user has personal Question & anser set + k_EAccountFlagVacBeta = ( 1 << 9 ), // user participates in VAC beta tests + k_EAccountFlagDebug = ( 1 << 10 ), // user is in debug mode, eg VAC doesn't kick etc + k_EAccountFlagDisabled = ( 1 << 11 ), // account is disabled. + k_EAccountFlagLimitedUser = ( 1 << 12 ), // account is limited user account because it doesnt own anything + k_EAccountFlagLimitedUserForce = ( 1 << 13 ), // account is limited user account because we forced it to be + k_EAccountFlagEmailValidated = ( 1 << 14 ), // user has verified email address via WG + k_EAccountFlagMarketingTreatment = ( 1 << 15), // account is flagged as being in a treatment for marketing/sales experiments +}; + +// profile state (stored in DB) +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ECommunityProfileState +{ + k_ECommunityProfileNotCreated = 0, // user hasn't setup community account yet + k_ECommunityProfileActive = 1, // user joined community, site is public + k_ECommunityProfilePrivate = 2, // user joined community, site is private + k_ECommunityProfileLocked = 3, // user got locked, content can't be changed but is still accessible + k_ECommunityProfileDisabled = 4, // user got disabled, site not accessible anymore +}; + + +// profile privacy option setting (stored in DB) +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ECommunityPrivacyState +{ + k_ECommunityPrivacyInvalid = 0, + k_ECommunityPrivacyPrivate = 1, // ain't nobody can see it + k_ECommunityPrivacyFriendsOnly = 2, // only your friends can see it + k_ECommunityPrivacyPublic = 3, // anybody could see it +}; + +enum ECommunityVisibilityState +{ + k_ECommunityVisibilityPrivate = 1, // private, requester see only public fields + k_ECommunityVisibilityFriendsOnly = 2, // friends only, requester sees only public fields + k_ECommunityVisibilityOpen = 3, // it is visible to requester; they are owner or friend or public + k_ECommunityVisibilitySupportPrivate = 4, // was private, but it's a support account asking + k_ECommunityVisibilitySupportFriendsOnly = 5,// was friends only, but it's a support account asking +}; + +enum ECommentPermission +{ + k_ECommentPermissionFriendsOnly = 0, // only friends can leave a comment + k_ECommentPermissionAnyone = 1, // anybody can leave a comment + k_ECommentPermissionSelfOnly = 2, // only the account owner can leave a comment +}; + +// Payment methods for purchases - BIT FLAGS so can be used to indicate +// acceptable payment methods for packages +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum EPaymentMethod +{ + k_EPaymentMethodNone = 0x000, // user got the license for free + k_EPaymentMethodActivationCode = 0x001, // user paid by entering unused CD-Key or other activation code + k_EPaymentMethodCreditCard = 0x002, // user paid with credit card + k_EPaymentMethodPayPal = 0x004, // user paid with via paypal + k_EPaymentMethodGuestPass = 0x008, // user paid by redeeming a guest pass + k_EPaymentMethodHardwarePromo = 0x010, // user presented machine credentials + k_EPaymentMethodClickAndBuy = 0x020, // ClickandBuy + k_EPaymentMethodAutoGrant = 0x040, // server side purchased package, things like German specific TF2 free weekend + k_EPaymentMethodWallet = 0x080, // user paid with wallet + k_EPaymentMethodOEMTicket = 0x100, // user paid by redeeming a OEM license ticket + k_EPaymentMethodSplit = 0x200, // user paid with wallet AND a provider +}; + +// Sources for WalletTxn records +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum EWalletSource +{ + k_EWalletSourceInvalid = 0, + k_EWalletSourcePurchase = 1, // Created from a purchase, refund, chargeback, or reverse chargeback (PurchaseRefGID -> TransID) + k_EWalletSourceGuestPass = 2, // Created from a guest pass (PurchaseRefGID -> GuestPassID) + k_EWalletSourceConversion = 3, // Created from a wallet conversion (PurchaseRefGID -> GID shared between debit & credit records) + k_EWalletSourceRebate = 4, // Created from a rebate (PurchaseRefGID -> TransID) +}; + +// License types +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ELicenseType +{ + k_ENoLicense = 0, // for shipped goods + k_ESinglePurchase = 1, // single purchase + k_ESinglePurchaseLimitedUse = 2, // single purchase w/ expiration + k_ERecurringCharge = 3, // recurring subscription + k_ERecurringChargeLimitedUse = 4, // recurring subscription w/ limited minutes per period + k_ERecurringChargeLimitedUseWithOverages = 5, // like above but w/ soft limit and overage charges +}; + +// Flags for licenses - BITS +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ELicenseFlags +{ + k_ELicenseFlagNone = 0x00, // just a place holder + k_ELicenseFlagRenew = 0x01, // Renew this license next period + k_ELicenseFlagRenewalFailed = 0x02, // Auto-renew failed + k_ELicenseFlagPending = 0x04, // Purchase or renewal is pending + k_ELicenseFlagExpired = 0x08, // Set if no longer active (whatever the reason) + k_ELicenseFlagCancelledByUser = 0x10, // Cancelled by the user + k_ELicenseFlagCancelledByAdmin = 0x20, // Cancelled by customer support + k_ELicenseFlagLowViolenceContent = 0x40,// license is for low violence content +}; + +// Status of a package +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum EPackageStatus +{ + k_EPackageAvailable = 0, // Available for purchase and use + k_EPackagePreorder = 1, // Available for purchase, as a pre-order + k_EPackageUnavailable = 2, // Not available for new purchases, may still be owned + k_EPackageInvalid = 3, // Either an unknown package or a deleted one that nobody should own +}; + +// Purchase status +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum EPurchaseStatus +{ + k_EPurchasePending = 0, // purchase is pending, valid but pending subscription + k_EPurchaseSucceeded = 1, // purchase successful, valid subscription + k_EPurchaseFailed = 2, // purchase failed, no subscription + k_EPurchaseRefunded = 3, // we refunded the purchase and removed subscription + k_EPurchaseInit = 4, // user started purchase + k_EPurchaseChargedback = 5, // the user issued a chargeback, we removed subscription + k_EPurchaseRevoked = 6, // we revoked the purchase and removed subscription. Usually stolen CD-Keys + k_EPurchaseInDispute = 7, // the purchase is being disputed by the user, preliminary to a chargeback +}; + +// LineItemTypes +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASEd +enum ELineItemType +{ + k_ELineItemTypeInvalid = 0, // Unknown - load all purchase line items + k_ELineItemTypeMicroTxn = ( 1 << 0 ), // Transaction has data in MicroTxnLineItem table + k_ELineItemTypeWallet = ( 1 << 1 ), // Transaction has data in WalletLineItem table + k_ELineItemTypePkg = ( 1 << 2 ), // Transaction has data in PurchaseLineItem table +}; + +// Enum for the types of news push items you can get +enum ENewsUpdateType +{ + k_EAppNews = 0, // news about a particular app + k_ESteamAds = 1, // Marketing messages + k_ESteamNews = 2, // EJ's corner and the like + k_ECDDBUpdate = 3, // backend has a new CDDB for you to load + k_EClientUpdate = 4, // new version of the steam client is available +}; + +// Detailed purchase result codes for the client +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum EPurchaseResultDetail +{ + k_EPurchaseResultNoDetail = 0, + k_EPurchaseResultAVSFailure = 1, + k_EPurchaseResultInsufficientFunds = 2, + k_EPurchaseResultContactSupport = 3, + k_EPurchaseResultTimeout = 4, + + k_EPurchaseResultInvalidPackage = 5, + k_EPurchaseResultInvalidPaymentMethod = 6, + k_EPurchaseResultInvalidData = 7, + k_EPurchaseResultOthersInProgress = 8, + k_EPurchaseResultAlreadyPurchased = 9, + k_EPurchaseResultWrongPrice = 10, + k_EPurchaseResultFraudCheckFailed = 11, + k_EPurchaseResultCancelledByUser = 12, + k_EPurchaseResultRestrictedCountry = 13, + k_EPurchaseResultBadActivationCode = 14, // this code gives no receipt + k_EPurchaseResultDuplicateActivationCode = 15, + + k_EPurchaseResultUseOtherPaymentMethod = 16, // User should try a different payment method + k_EPurchaseResultUseOtherFundingSource = 17, // Select a different funding source (paypal) + k_EPurchaseResultInvalidShippingAddress = 18, // Shipping address is invalid (paypal) + k_EPurchaseResultRegionNotSupported = 19, // This region is not supported with this payment type + + k_EPurchaseResultAcctIsBlocked = 20, // Acct has been blocked by provider - user should contact provider to resolve + k_EPurchaseResultAcctNotVerified = 21, // Provider indicated account needs to be verified for transaction to complete + + k_EPurchaseResultInvalidAccount = 22, // Provider indicated the account is invalid or no longer usable + k_EPurchaseResultStoreBillingCountryMismatch = 23, // store country code & billing country code do not match + k_EPurchaseResultDoesNotOwnRequiredApp = 24, // user does not own one of the apps required for purchase + k_EPurchaseResultCanceledByNewTransaction = 25, // user made a new transaction which canceled an old, pending transaction + k_EPurchaseResultForceCanceledPending = 26, // A pending transaction was force canceled (no response from provider) + k_EPurchaseResultFailCurrencyTransProvider = 27, // selected transaction provider does not support calculated currency + k_EPurchaseResultFailedCyberCafe = 28, // cybercafe account tried to purchase or use an activation code + + k_EPurchaseResultNeedsPreApproval = 29, // Transaction needs approval from support + k_EPurchaseResultPreApprovalDenied = 30, // Transaction was denied by support + k_EPurchaseResultWalletCurrencyMismatch = 31, // Currency of purchase does not match currency of wallet +}; + +// Type of system IM. The client can use this to do special UI handling in specific circumstances +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ESystemIMType +{ + k_ESystemIMRawText = 0, + k_ESystemIMInvalidCard = 1, + k_ESystemIMRecurringPurchaseFailed = 2, + k_ESystemIMCardWillExpire = 3, + k_ESystemIMSubscriptionExpired = 4, + k_ESystemIMGuestPassReceived = 5, // User has received a guest pass from a friend + k_ESystemIMGuestPassGranted = 6, // System has granted a user a guest pass to give out + k_ESystemIMGiftRevoked = 7, // We revoked a gift due to chargeback, etc + + // + k_ESystemIMTypeMax +}; + +// Ways an external cd key can be munged onto a users PC +enum ELegacyKeyRegistrationMethod +{ + eLegacyKeyRegistrationMethodNone = 0, // doesn't support legacy cd keys + eLegacyKeyRegistrationMethodRegistry, // just place it into the registry + eLegacyKeyRegistrationMethodDisk, // put it in a file on disk +}; + +// Support events, generated by system or support input +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ESupportEvent +{ + // support activity + eSupportNote = 0, // a generic support note + eSupportLogin = 1, // support account logged in out, data: IP:Port + eSupportLogoff = 2, // support account logged out, text: IP:Port + eSupportTicketCreated = 3, // a support ticket was created + eSupportTicketClosed = 4, // problem was solved, ticket closed + + // account changes + eSupportEnableAccount = 5, // support enabled account, text: reason + eSupportDisableAccount = 6, // support disabled account, text: reason + eSupportChangeAccountPassword = 7, // support or user changed password, data: DONT include old password + eSupportChangeAccountEmail = 8, // support or user changed email, data: old email + eSupportChangeAccountName = 9, // support or user changed name, data: old name + eSupportChangeAccountPlayer = 10, // support or user changed player name, data: old name + + eSupportPurchaseChargedback = 11, // a charge back was issued + eSupportPurchaseRefunded = 12, // a refund was issued + eSupportPurchaseForcedCompletion = 13, // support forced a pending purchase to complete + + // license handling + eSupportLicenseAdded = 14, // support added a license, text: reason + eSupportLicenseCanceled = 15, // support or user cancel a license + eSupportLicenseChanged = 16, // support removed a license, text: reason + eSupportBannedGame = 17, // support banned game for an account + eSupportUnbannedGame = 18, // support unbanned game for an account + + // purchase activity + eSupportRunPurchase = 19, + eSupportChangedCreditCard = 20, // support updated a credit card + eSupportSetNoFraudCheckFlag = 21, // support disabled fraud check, data: reason + + // banning + eSupportBannedCreditCard = 22, // support banned a credit card + eSupportBannedIP = 23, // support banned an IP + eSupportBannedCDKey = 24, // support banned an CDKey + eSupportBannedCountry = 25, // support banned a country + eSupportBannedPayPalAccount = 26, + + eSupportPurchaseCanceled = 27, // support forced a pending purchase to cancel + + eSupportChangeAvatar = 28, + eSupportChangeProfileURL = 29, + + eSupportRegisterCDKey = 30, // support added a CD key to this account + eSupportGrantGuestPass = 31, // support granted a guest pass to this account + eSupportResubmitTransaction = 32, // support resubmitted a transaction + + eSupportResetContent = 33, // reset user content based on abuse reports + eSupportLockProfile = 34, // temp block a user from modifying community content + eSupportSetCommunityState = 35, // perm lock a user profile, can't be modified + eSupportDeleteAbuseReports = 36, // deleted abuse reports for the SteamID + eSupportSetAccountFlags = 37, // changed account flags + + eSupportChargebackStatusUpdate = 38, // support updated the pending chargeback status + + eSupportRefundForcedCompletion = 39, // support forced a pending refund to complete + eSupportRefundCanceled = 40, // support forced a pending refund to cancel + + eSupportRevokeActivationKey = 41, // support revoked and unlocked activation key + eSupportReverseChargeback = 42, // a charge back was reversed + eSupportRejectedActivationKey = 43, // activation key was rejected (invalid or already used) + + eSupportPurchaseError = 44, // Purchase error + eSupportAudited = 45, + + // items + eSupportBanItems = 46, // support banned a user's items + eSupportRestoreBannedItems = 47, // support restored banned items + eSupportRestoreDeletedItems = 48, // support restored deleted items + + // comments + eSupportDeleteComments = 49, // cleared comments on this account (not written by this account) + + eSupportDeleteItems = 50, // support deleted a user's items + eSupportDeleteCachedCard = 51, // support deleted a user's cached credit card + + eSupportConvertedWallet = 52, // user's wallet was converted + + eSupportTxnApproved = 53, // PreApproval granted + eSupportTxnDenied = 54, // PreApproval denied + + eSupportGCAction = 55, // used for all support actions from the GC +}; + + +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ESupportTicket +{ + // all kinds of problems tickets that have to be handled by support + k_ETicketUnknown = 0, // an unknown problem. yay. + k_ETicketManual = 1, // a problem manually entered by support. + k_ETicketFraudRedFlag = 2, // fraud detection marked this account + k_ETicketPurchaseError = 3, // a purchase error happened + k_ETicketChargeback = 4, // a chargeback needs attention +}; + +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ESupportTicketState +{ + // all kinds of problems tickets that have to be handled by support + k_ETicketStateUnknown = 0, // support issue state is unknown + k_ETicketStateUnassigned = 1, // support issue is 'open' but not assigned yet + k_ETicketStateInProcess = 2, // support issue is assigned to an support actor + k_ETicketStateResolved = 3, // support issue is fixed and closed + k_ETicketStateUnresolved = 4, // support issue couldn't be fixed. closed anyway + k_ETicketStateAutoClosed = 5, // System closed the ticket automatically +}; + + +//----------------------------------------------------------------------------- +// types of content that can be reported as abused +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +//----------------------------------------------------------------------------- +enum ECommunityContentType +{ + k_EContentUnspecified = 0, + k_EContentAll = 1, // reset all community content + k_EContentAvatarImage = 2, // clear avatar image + k_EContentProfileText = 3, // reset profile text + k_EContentWebLinks = 4, // delete web links + k_EContentAnnouncement = 5, + k_EContentEventText = 6, + k_EContentCustomCSS = 7, + k_EContentProfileURL = 8, // delete community URL ID + k_EContentComments = 9, // just comments this guy has written +}; + + +//----------------------------------------------------------------------------- +// types of reasons why a violation report was issued +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +//----------------------------------------------------------------------------- +enum EAbuseReportType +{ + k_EAbuseUnspecified = 0, + k_EAbuseInappropriate = 1, // just not ok to post + k_EAbuseProhibited = 2, // prohibited by EULA or general law + k_EAbuseSpamming = 3, // excessive spamming + k_EAbuseAdvertisement = 4, // unwanted advertisement + k_EAbuseExploit = 5, // content data attempts to exploit code issue + k_EAbuseSpoofing = 6, // user/group is impersonating an official contact + k_EAbuseLanguage = 7, // bad language + k_EAbuseAdultContent = 8, // any kind of adult material, references etc + k_EAbuseHarassment = 9, // harassment, discrimination, racism etc +}; + +//----------------------------------------------------------------------------- +// actions for a user within a clan for logging in the ClanHistory table +//----------------------------------------------------------------------------- + +enum EClanAction +{ + k_EJoined = 1, // joined the clan + k_ELeft = 2, // left the clan + k_EPromoted = 3, // promoted to officer + k_EDemoted = 4, // demoted from officer + k_EKicked = 5, // kicked off the clan + k_ECreated = 6, // clan was created + k_EInvited = 7, // invited someone + k_EEventCreated = 8, // clan event created + k_EEventUpdated = 9, // clan event updated + k_EEventDeleted = 10, // clan event deleted + k_EPermissionsChanged = 11, // clan permissions were changed + k_EAnnouncementCreated = 12, // clan announcement created + k_EAnnouncementUpdated = 13, // clan announcement updated + k_EAnnouncementDeleted = 14, // clan announcement deleted + k_EPOTWChanged = 15, // changed the POTW + k_ELinksChanged = 16, // links changed + k_EDetailsChanged = 17, // details changed + k_ESupportResetContent = 18, // support reset some or all of the clan content + k_ESupportLockedGroup = 19, // support locked this clan, it can't be modified anymore + k_ESupportUnlockedGroup = 20, // support unlocked this clan + k_ESupportChangedOwner = 21, // support transfered ownership + k_EMadePublic = 22, // made from private into public + k_EMadePrivate = 23, // made from public into private + k_ESupportDisabledGroup = 24, // support disabled this group, nobody can see it anymore + k_EKickedChat = 25, // kicked from chat + k_EBannedChat = 26, // banned from chat + k_EUnBannedChat = 27, // un-banned from chat + + k_EHighestValidAction // keep me updated, please! +}; + + +//----------------------------------------------------------------------------- +// types of events for use in the Clan Event Type table +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +//----------------------------------------------------------------------------- +enum EClanEventType +{ + k_EOtherEvent = 1, + k_EGameEvent = 2, + k_EPartyEvent = 3, + k_EMeetingEvent = 4, + k_ESpecialCauseEvent = 5, + k_EMusicAndArtsEvent = 6, + k_ESportsEvent = 7, + k_ETripEvent = 8, + k_EChatEvent = 9, + k_EGameReleaseEvent = 10, +}; + +//----------------------------------------------------------------------------- +// types of marketing messages displayed to users +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +//----------------------------------------------------------------------------- +enum EMarketingMessageType +{ + k_EMarketingMessageNowAvailable = 1, + k_EMarketingMessageWeekendDeal = 2, + k_EMarketingMessagePrePurchase = 3, + k_EMarketingMessagePlayNow = 4, + k_EMarketingMessagePreloadNow = 5, + k_EMarketingMessageGeneral = 6, + k_EMarketingMessageDemoQuit = 7, + k_EMarketingMessageGifting = 8, + k_EMarketingMessageEJsKorner = 9, +}; + +//----------------------------------------------------------------------------- +// types of associations a marketing message may have +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +//----------------------------------------------------------------------------- +enum EMarketingMessageAssociationType +{ + k_EMarketingMessageNoAssociation = 0, + k_EMarketingMessageAppAssociation = 1, + k_EMarketingMessageSubscriptionAssociation = 2, + k_EMarketingMessagePublisherAssociation = 3, + k_EMarketingMessageGenreAssociation = 4, +}; + +//----------------------------------------------------------------------------- +// Marketing message visibility +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +//----------------------------------------------------------------------------- +enum EMarketingMessageVisibility +{ + k_EMarketingMessageVisibleBeta = 1, + k_EMarketingMessageVisiblePublic = 2, +}; + +//----------------------------------------------------------------------------- +// Structures used in multiple messages +//----------------------------------------------------------------------------- +// Purchase message constants +// WARNING: Do not change these if an instance of this record may exist in a database!!! +// BUGBUG derrick - Since these also define schema, they should be moved into steamschema.h +const int k_cchCCNumMax = 16 + 1; +const int k_cchHolderNameMax = 100 + 1; +const int k_cchExpYearMax = 4 + 1; +const int k_cchExpMonthMax = 2 + 1; +const int k_cchCVV2Max = 4 + 1; +const int k_cchAddressMax = 128 + 1; +const int k_cchAddress2Max = k_cchAddressMax; +const int k_cchCityMax = 50 + 1; +const int k_cchPostcodeMax = 16 + 1; +const int k_cchStateMax = 32 + 1; +const int k_cchPhoneMax = 20 + 1; +const int k_cchEmailMax = 100 + 1; +const int k_cchCountryCodeMax = 2 + 1; +const int k_cchPayPalCheckoutTokenMax = 20 + 1; +const int k_cchStateCodeMax = 3 + 1; +const int k_cchCurrencyCodeMax = 3 + 1; + +const int k_cubMaxDfsURL = 128; // Max size for URL descriptors on DFS + + +// License information +struct LicenseInfo_t +{ + PackageId_t m_unPackageID; + RTime32 m_RTime32Created; + RTime32 m_RTime32NextProcess; + int32 m_nMinuteLimit; + int32 m_nMinutesUsed; + EPaymentMethod m_ePaymentMethod; + uint32 m_nFlags; + char m_rgchPurchaseCountryCode[k_cchCountryCodeMax]; + int32 m_nTerritoryCode; +}; + +// Supported Currency Codes +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ECurrencyCode +{ + k_ECurrencyCodeInvalid = 0, + k_ECurrencyCodeUSD = 1, + k_ECurrencyCodeGBP = 2, + k_ECurrencyCodeEUR = 3, + + k_ECurrencyCodeMax = 4 +}; + +enum ETaxType +{ + k_ETaxTypeInvalid = 0, + k_ETaxTypeUSState = 1, + k_ETaxTypeVAT = 2 +}; + +// client stat list +// needs to be kept in the same order, since it's part of the protocol +enum EClientStat +{ + k_EClientStatP2PConnectionsUDP = 0, + k_EClientStatP2PConnectionsRelay = 1, + k_EClientStatP2PGameConnections = 2, + k_EClientStatP2PVoiceConnections = 3, + + k_EClientStatBytesDownloaded = 4, + + k_EClientStatMax, // must be last, used as array's of data +}; + +enum EP2PState +{ + k_EP2PStateNotConnected, + k_EP2PStateUDP, + k_EP2PStateRelay, +}; + +// User response for authentication request +enum EMicroTxnAuthResponse +{ + k_EMicroTxnAuthResponseInvalid = 0, // Invalid value + k_EMicroTxnAuthResponseAuthorize = 1, // user accepted microtransaction + k_EMicroTxnAuthResponseDeny = 2, // user denied microtransaction + k_EMicroTxnAuthResponseAutoDeny = 3, // client automatically denied microtransaction (user wasn't in game, etc.) +}; + +// Result of authorization request, returned to client +enum EMicroTxnAuthResult +{ + k_EMicroTxnAuthResultInvalid = 0, // Invalid value + k_EMicroTxnAuthResultOK = 1, // Successfully authorized + k_EMicroTxnAuthResultFail = 2, // An error occurred + k_EMicroTxnAuthResultInsufficientFunds = 3, // User has insufficient funds to complete microtransaction +}; + +#endif diff --git a/gcsdk/steamextra/gamecoordinator/igamecoordinator.h b/gcsdk/steamextra/gamecoordinator/igamecoordinator.h new file mode 100644 index 0000000..80eb7a4 --- /dev/null +++ b/gcsdk/steamextra/gamecoordinator/igamecoordinator.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef IGAMECOORDINATOR_H +#define IGAMECOORDINATOR_H +#ifdef _WIN32 +#pragma once +#endif + +typedef uint32 AppId_t; +class CSteamID; +class IGameCoordinatorHost; +class IGCSQLResultSetList; + +class IGameCoordinator +{ +public: + virtual bool BInit( AppId_t unAppID, const char *pchAppPath, IGameCoordinatorHost *pHost ) = 0; + virtual bool BMainLoopOncePerFrame( uint64 ulLimitMicroseconds ) = 0; + virtual bool BMainLoopUntilFrameCompletion( uint64 ulLimitMicroseconds ) = 0; + virtual void Shutdown() = 0; + virtual void Uninit() = 0; + virtual void MessageFromClient( const CSteamID & senderID, uint32 unMsgType, void *pubData, uint32 cubData ) = 0; + virtual void Validate( CValidator &validator, const char *pchName ) = 0; + virtual void SQLResults( GID_t gidContextID ) = 0; +}; + +#define GAMECOORDINATOR_INTERFACE_VERSION "GAMECOORDINATOR003" + +#endif // IGAMECOORDINATOR_H diff --git a/gcsdk/steamextra/gamecoordinator/igamecoordinatorhost.h b/gcsdk/steamextra/gamecoordinator/igamecoordinatorhost.h new file mode 100644 index 0000000..d94f306 --- /dev/null +++ b/gcsdk/steamextra/gamecoordinator/igamecoordinatorhost.h @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Provides an interface that the server hosting a GC must implement +// +// $NoKeywords: $ +//============================================================================= + +#ifndef IGAMECOORDINATORHOST_H +#define IGAMECOORDINATORHOST_H +#ifdef _WIN32 +#pragma once +#endif + +class CSteamID; +class IGCSQLQuery; + +class IGameCoordinatorHost +{ +public: + virtual bool BSendMessageToClient( AppId_t unAppID, const CSteamID & steamIDTarget, uint32 unMsgType, const void *pubData, uint32 cubData ) = 0; + virtual GID_t GenerateGID() = 0; + virtual void EmitMessage( const char *pchGroupName, SpewType_t spewType, int iSpewLevel, int iLevelLog, const char *pchMsg ) = 0; + virtual void SQLQuery( GID_t gidContextID, IGCSQLQuery *pQuery, int eSchemaCatalog ) = 0; + virtual void StartupComplete( bool bSuccess ) = 0; + virtual void ShutdownComplete() = 0; + virtual EUniverse GetUniverse() = 0; +}; + +#define GAMECOORDINATORHOST_INTERFACE_VERSION "GAMECOORDINATORHOST002" + +#endif // IGAMECOORDINATORHOST_H diff --git a/gcsdk/steamextra/gamecoordinator/igcsqlquery.h b/gcsdk/steamextra/gamecoordinator/igcsqlquery.h new file mode 100644 index 0000000..cdfd3ad --- /dev/null +++ b/gcsdk/steamextra/gamecoordinator/igcsqlquery.h @@ -0,0 +1,67 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef IGCSQLQUERY_H +#define IGCSQLQUERY_H +#ifdef _WIN32 +#pragma once +#endif + + +// the type of the parameter +enum EGCSQLType +{ + k_EGCSQLTypeInvalid = -1, + + // Variable length types + k_EGCSQLType_Blob, + k_EGCSQLType_String, + + // fixed length types + k_EGCSQLType_int8, // also uint8 + k_EGCSQLType_int16, // also uint16 + k_EGCSQLType_int32, // also uint32 + k_EGCSQLType_int64, // also uint64 + k_EGCSQLType_float, + k_EGCSQLType_double, + k_EGCSQLType_Binary, // raw binary data of fixed size (i.e. a C struct). + k_EGCSQLType_Image, + k_EGCSQLType_bool, +}; + +class IGCSQLResultSetList; + +class IGCSQLQuery +{ +protected: + // call Destroy() instead of deleting this object directly + virtual ~IGCSQLQuery() {} + +public: + // returns the number of statements in the transaction + // represented by this query object + virtual uint32 GetStatementCount() = 0; + + // returns a string that represents where in the GC this + // query came from. Usually this is FILE_AND_LINE. + virtual const char *PchName() = 0; + + // get the null-terminated query string itself + virtual const char *PchCommand( uint32 unStatement ) = 0; + + // gets the parameter data + virtual uint32 CnParams( uint32 unStatement ) = 0; + virtual EGCSQLType EParamType( uint32 unStatement, uint32 uIndex ) = 0; + virtual byte *PubParam( uint32 unStatement, uint32 uIndex ) = 0; + virtual uint32 CubParam( uint32 unStatement, uint32 uIndex ) = 0; + + // reports the result + virtual void SetResults( IGCSQLResultSetList *pResults ) = 0; +}; + + +#endif // IGCSQLQUERY_H diff --git a/gcsdk/steamextra/gamecoordinator/igcsqlresultsetlist.h b/gcsdk/steamextra/gamecoordinator/igcsqlresultsetlist.h new file mode 100644 index 0000000..ced12f2 --- /dev/null +++ b/gcsdk/steamextra/gamecoordinator/igcsqlresultsetlist.h @@ -0,0 +1,62 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef IGCSQLRESULTSETLIST_H +#define IGCSQLRESULTSETLIST_H +#ifdef _WIN32 +#pragma once +#endif + +#include "igcsqlquery.h" + +enum EGCSQLError +{ + k_EGCSQLErrorNone = 0, + k_EGCSQLErrorUnknown, + k_EGCSQLErrorBacklog, + k_EGCSQLErrorBadQueryParameters, + k_EGCSQLErrorConnectionError, + k_EGCSQLErrorDataTruncated, + k_EGCSQLErrorDeadlockLoser, + k_EGCSQLErrorDuplicateKey, + k_EGCSQLErrorGenericError, + k_EGCSQLErrorNoResultSet, + k_EGCSQLErrorSyntaxError, + k_EGCSQLErrorTableOrViewNotFound, + k_EGCSQLErrorTimeout, + k_EGCSQLErrorConstraintViolation, + k_EGCSQLErrorNumericValueOutOfRange, + k_EGCSQLErrorRollbackFailed, + k_EGCSQLErrorColumnNotFound, +}; + + +class IGCSQLResultSet +{ +public: + virtual uint32 GetColumnCount() = 0; + virtual EGCSQLType GetColumnType( uint32 nColumn ) = 0; + virtual const char *GetColumnName( uint32 nColumn ) = 0; + + virtual uint32 GetRowCount() = 0; + virtual bool GetData( uint32 unRow, uint32 unColumn, uint8 **ppData, uint32 *punSize ) = 0; +}; + + +class IGCSQLResultSetList +{ +public: + virtual EGCSQLError GetError() = 0; + virtual uint32 GetResultSetCount() = 0; + virtual IGCSQLResultSet *GetResultSet( uint32 nResultSetIndex ) = 0; + virtual uint32 GetRowsAffected( uint32 unWhichStatement ) = 0; + virtual void Destroy() = 0; + virtual const char *GetErrorText() = 0; +}; + + +#endif // IGCSQLRESULTSETLIST_H diff --git a/gcsdk/steamextra/misc.cpp b/gcsdk/steamextra/misc.cpp new file mode 100644 index 0000000..ed3facb --- /dev/null +++ b/gcsdk/steamextra/misc.cpp @@ -0,0 +1,187 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Miscellaneous code +// +//============================================================================= + +#include "stdafx.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Purpose: Tells us whether an account name looks like a VTT account name +// (used as an exception for IP-based rate limiting) +//----------------------------------------------------------------------------- +bool IsVTTAccountName( const char *szAccountName ) +{ + const static char *k_szCafe = "valvecafepc"; + + if ( 0 == Q_strncmp( szAccountName, k_szCafe, Q_strlen( k_szCafe ) ) ) + return true; + return false; +} + + +//----------------------------------------------------------------------------- +// Random number generation +// Use this system for all random number generation. It's very fast, and is +// integrated with our automated tests so that we can perform reproducibly "random" +// test cases. +//----------------------------------------------------------------------------- +uint32 g_unRandCur = 0; +int g_iunRandMask = 0; + +// k_rgunMask +// Set of masks used by our quick and dirty random number generator. +const uint32 k_rgunMask[17] = +{ + 0x1739a3b0, + 0xb8907fe1, + 0x8290d3b7, + 0x72839cd0, + 0x242df096, + 0x3829750b, + 0x38de7a77, + 0x72f0924c, + 0x44783927, + 0x01925372, + 0x20902714, + 0x27585920, + 0x27890632, + 0x82910476, + 0x72906721, + 0x28798904, + 0x78592700, +}; + + +//----------------------------------------------------------------------------- +// Purpose: Quickly generates a vaguely random number. +// Output: A vaguely random number. +//----------------------------------------------------------------------------- +uint32 UNRandFast() +{ + g_iunRandMask++; + g_unRandCur += 637429601; // Just add a large prime number (we'll wrap frequently) + return ( g_unRandCur ^ k_rgunMask[ g_iunRandMask % 17 ] ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Quickly generates a vaguely random character. +// Output: A vaguely random char in the range [32,126]. +//----------------------------------------------------------------------------- +char CHRandFast() +{ + return ( UNRandFast() % 95 ) + 32; +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the random number seed (note that we actually break this down +// into two parts: g_unRandCur and g_iunRandMask). +// Input: ulRandSeed: Value to use as our seed +//----------------------------------------------------------------------------- +void SetRandSeed( uint64 ulRandSeed ) +{ + g_unRandCur = ulRandSeed >> 32; + g_iunRandMask = ulRandSeed & 0xffffffff; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the current random number seed (actually a composite of +// g_unRandCur and g_iunRandMask) +// Output: Our current 64 bit random number seed. +//----------------------------------------------------------------------------- +uint64 GetRandSeed() +{ + return ( ( ((uint64)g_unRandCur) << 32 ) + g_iunRandMask ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Quickly fill a memory block with random bytes +//----------------------------------------------------------------------------- +void RandMem(void *dest, int count) +{ + unsigned char *pDest = (unsigned char *)dest; + + while ( count >= 4 ) + { + *(uint32*)(pDest) = UNRandFast(); + pDest+=4; + count-=4; + } + + while ( count > 0 ) + { + *pDest = UNRandFast(); + pDest++; + count--; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculates the percentage of numerator/demoninator, or 0 if +// denominator is 0. +//----------------------------------------------------------------------------- +float SafeCalcPct( uint64 ulNumerator, uint64 ulDenominator ) +{ + if ( 0 == ulDenominator ) + return 0; + return ( 100.0f * (float) ulNumerator / (float) ulDenominator ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Common code to reject an operation due to a time backlog. +// Does a gradual fade of 0% rejections at the specified threshold +// up to 100% at the limit. The gradual fade reduces system oscillations +// that could occur if you abruptly stop allowing all operations. +// Input: nBacklogCur - the current backlog (in arbitrary units) +// nBacklogThreshold - the threshold backlog at which to begin rejecting +// nBacklogLimit - hard limit at which to reject 100% of operations +// iItem - a monotonically increasing counter of items submitted. Used +// to determine which operations are allowed if there is a partial +// rejection rate. +//----------------------------------------------------------------------------- +bool BRejectDueToBacklog( int nBacklogCur, int nBacklogThreshold, int nBacklogLimit, int iItem ) +{ + bool bRefuse = false; + + if ( nBacklogCur >= nBacklogLimit ) + { + // if we're over the hard backlog limit, refuse all operations + bRefuse = true; + } + else if ( nBacklogCur >= nBacklogThreshold ) + { + // if we're near the hard backlog limit, start refusing an increasing % of operations, + // so we don't snap abruptly in and out of accepting operations and potentially cause oscillations + + // ramp from refusing 0% of operations at the backlog threshold up to 100% at the backlog hard limit + + // calculate refuse % to nearest 10%, to make it easy to mod the item # and get a good distribution + float nRefusePctDecile = 10 * (float) ( nBacklogCur - nBacklogThreshold ) / + (float) ( nBacklogLimit - nBacklogThreshold ); + Assert( nRefusePctDecile >= 0.0 ); + Assert( nRefusePctDecile <= 10.0 ); + + // compare the operations submitted count mod 10 to the refusal percent decile to decide if we should + // accept or refuse this particular operation + if ( ( iItem % 10 ) < nRefusePctDecile ) + bRefuse = true; + } + + return bRefuse; +} + + +//----------------------------------------------------------------------------- +// Purpose: Defines the head of the CDumpMemFnReg linked list +//----------------------------------------------------------------------------- +CDumpMemFnReg *CDumpMemFnReg::sm_Head = NULL;
\ No newline at end of file diff --git a/gcsdk/steamextra/misc.h b/gcsdk/steamextra/misc.h new file mode 100644 index 0000000..d1ced65 --- /dev/null +++ b/gcsdk/steamextra/misc.h @@ -0,0 +1,121 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Miscellaneous platform-specific code +// +//============================================================================= + + +#ifndef MISC_H +#define MISC_H + +// Random number utilities +uint32 UNRandFast(); +char CHRandFast(); +void SetRandSeed( uint64 ulRandSeed ); +uint64 GetRandSeed(); +void RandMem(void *dest, int count); + +bool IsVTTAccountName( const char *szAccountName ); + +float SafeCalcPct( uint64 ulNumerator, uint64 ulDenominator ); + +bool BRejectDueToBacklog( int nBacklogCur, int nBacklogThreshold, int nBacklogLimit, int iItem ); + +#define SAFE_CLOSE_HANDLE( x ) if ( INVALID_HANDLE_VALUE != ( x ) ) { CloseHandle( x ); ( x ) = INVALID_HANDLE_VALUE; } +#define SAFE_DELETE( x ) if ( NULL != ( x ) ) { delete ( x ); ( x ) = NULL; } +#define SAFE_CLOSE_HCONNECTION( x ) if ( 0 != ( x ) ) { CNet::BClose( ( x ) ); ( x ) = 0; } +#define SAFE_RELEASE( x ) if ( NULL != ( x ) ) { ( x )->Release(); x = NULL; } + +#define DECLARE_STEAM_CLASS_SIMPLE( className, baseClassName ) \ + typedef baseClassName BaseClass; \ + typedef className ThisClass; \ + +#define DECLARE_STEAM_CLASS_NOBASE( className ) \ + typedef className ThisClass; \ + +// Type for our memory output debugging. +class IDisplayMemPoolStats +{ +public: + virtual void Display( const char *pszClassName, uint64 ulClassSize, uint32 unPoolInstanceCount, uint64 ulPoolMemoryUsage, uint32 unPoolPeakInstanceCount, uint64 ulPoolPeakMemoryUsage ) = 0; +}; + +// A class for registering functions that will print memory usage information +typedef void (*DumpMemFn_t)( IDisplayMemPoolStats * ); +class CDumpMemFnReg +{ +public: + static CDumpMemFnReg *sm_Head; + + CDumpMemFnReg( DumpMemFn_t fn ) + : m_fn( fn ), + m_pNext( sm_Head ) + { + sm_Head = this; + } + + DumpMemFn_t m_fn; + CDumpMemFnReg *m_pNext; +}; + +// Helper macros for creating and using CClassMemoryPool on our frequently allocated objects +#define DECLARE_CLASS_MEMPOOL( className ) \ +private: \ + static CUtlMemoryPool sm_classMemPool; \ +public: \ + static void* operator new ( size_t nSize ); \ + static void* operator new ( size_t nSize, int nBlockUse, const char *pFileName, int nLine ); \ + static void operator delete ( void *pMem ); \ + static void DumpMemStats( IDisplayMemPoolStats *pDisplayer ); \ + + +#define IMPLEMENT_CLASS_MEMPOOL( className, initSize, growMode ) \ +CUtlMemoryPool className::sm_classMemPool( sizeof( className ), ( initSize ), ( growMode ), MEM_ALLOC_CLASSNAME( className ) ); \ + \ +void* className::operator new ( size_t nSize ) \ +{ \ + if ( nSize != sizeof( className ) ) \ + { \ + EmitError( SPEW_CONSOLE, #className"::operator new() called on wrong size! Expected %llu, Got %llu\n", (uint64)sizeof( className ), (uint64)nSize ); \ + return NULL; \ + } \ + \ + return sm_classMemPool.Alloc(); \ +} \ + \ +void* className::operator new ( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) \ +{ \ + if ( nSize != sizeof( className ) ) \ + { \ + EmitError( SPEW_CONSOLE, #className"::operator new() called on wrong size! Expected %llu, Got %llu\n", (uint64)sizeof( className ), (uint64)nSize ); \ + return NULL; \ + } \ + \ + return sm_classMemPool.Alloc(); \ +} \ + \ +void className::operator delete ( void *pMem ) \ +{ \ + sm_classMemPool.Free( (className *)pMem ); \ +} \ + \ +void className::DumpMemStats( IDisplayMemPoolStats *pDisplayer ) \ +{ \ + Assert( pDisplayer ); \ + pDisplayer->Display \ + ( \ + #className, \ + sizeof( className ), \ + sm_classMemPool.Count(), \ + sm_classMemPool.Count() * sizeof( className ), \ + ( ( sm_classMemPool.PeakCount() + ( initSize ) ) / ( initSize ) ) * ( initSize ) , \ + ( ( sm_classMemPool.PeakCount() + ( initSize ) ) / ( initSize ) ) * ( initSize ) * sizeof( className ) \ + ); \ +} \ + \ +static CDumpMemFnReg s_##className##RegDumpMemory( &className::DumpMemStats ); + +// useful macro for rendering an IP +#define iptod(x) ((x)>>24&0xff), ((x)>>16&0xff), ((x)>>8&0xff), ((x)&0xff) + +#endif // MISC_H diff --git a/gcsdk/steamextra/rtime.cpp b/gcsdk/steamextra/rtime.cpp new file mode 100644 index 0000000..14081ce --- /dev/null +++ b/gcsdk/steamextra/rtime.cpp @@ -0,0 +1,1216 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Encapsulates real world (wall clock) time +// +//============================================================================= + +#include "stdafx.h" +#ifdef POSIX +#include <sys/time.h> +#else +#include "winlite.h" +#endif +#include "rtime.h" +#include <time.h> + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if defined( WIN32 ) || defined( _PS3 ) +// This strptime implementation is taken from the Goolge Site Map Generator project: + +// Copyright 2009 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Implement strptime under windows +static const char* kWeekFull[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" +}; + +static const char* kWeekAbbr[] = { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" +}; + +static const char* kMonthFull[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +}; + +static const char* kMonthAbbr[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static const char* _parse_num(const char* s, int low, int high, int* value) { + const char* p = s; + for (*value = 0; *p != NULL && V_isdigit(*p); ++p) { + *value = (*value) * 10 + static_cast<int>(*p) - static_cast<int>('0'); + } + + if (p == s || *value < low || *value > high) return NULL; + return p; +} + +static char* _strptime(const char *s, const char *format, struct tm *tm) { + while (*format != NULL && *s != NULL) { + if (*format != '%') { + if (*s != *format) return NULL; + + ++format; + ++s; + continue; + } + + ++format; + int len = 0; + switch (*format) { + // weekday name. + case 'a': + case 'A': + tm->tm_wday = -1; + for (int i = 0; i < 7; ++i) { + len = static_cast<int>(strlen(kWeekAbbr[i])); + if (V_strnicmp(kWeekAbbr[i], s, len) == 0) { + tm->tm_wday = i; + break; + } + + len = static_cast<int>(strlen(kWeekFull[i])); + if ( V_strnicmp(kWeekFull[i], s, len) == 0) { + tm->tm_wday = i; + break; + } + } + if (tm->tm_wday == -1) return NULL; + s += len; + break; + + // month name. + case 'b': + case 'B': + case 'h': + tm->tm_mon = -1; + for (int i = 0; i < 12; ++i) { + len = static_cast<int>(strlen(kMonthAbbr[i])); + if ( V_strnicmp(kMonthAbbr[i], s, len) == 0) { + tm->tm_mon = i; + break; + } + + len = static_cast<int>(strlen(kMonthFull[i])); + if ( V_strnicmp(kMonthFull[i], s, len) == 0) { + tm->tm_mon = i; + break; + } + } + if (tm->tm_mon == -1) return NULL; + s += len; + break; + + // month [1, 12]. + case 'm': + s = _parse_num(s, 1, 12, &tm->tm_mon); + if (s == NULL) return NULL; + --tm->tm_mon; + break; + + // day [1, 31]. + case 'd': + case 'e': + s = _parse_num(s, 1, 31, &tm->tm_mday); + if (s == NULL) return NULL; + break; + + // hour [0, 23]. + case 'H': + s = _parse_num(s, 0, 23, &tm->tm_hour); + if (s == NULL) return NULL; + break; + + // minute [0, 59] + case 'M': + s = _parse_num(s, 0, 59, &tm->tm_min); + if (s == NULL) return NULL; + break; + + // seconds [0, 60]. 60 is for leap year. + case 'S': + s = _parse_num(s, 0, 60, &tm->tm_sec); + if (s == NULL) return NULL; + break; + + // year [1900, 9999]. + case 'Y': + s = _parse_num(s, 1900, 9999, &tm->tm_year); + if (s == NULL) return NULL; + tm->tm_year -= 1900; + break; + + // year [0, 99]. + case 'y': + s = _parse_num(s, 0, 99, &tm->tm_year); + if (s == NULL) return NULL; + if (tm->tm_year <= 68) { + tm->tm_year += 100; + } + break; + // arbitray whitespace. + case 't': + case 'n': + while (V_isspace(*s)) ++s; + break; + + // '%'. + case '%': + if (*s != '%') return NULL; + ++s; + break; + + // All the other format are not supported. + default: + AssertMsg( false, "Invalid format string to strptime!" ); + return NULL; + } + ++format; + } + + if (*format != NULL) { + return NULL; + } else { + return const_cast<char*>(s); + } +} + +char* strptime(const char *buf, const char *fmt, struct tm *tm) { + return _strptime(buf, fmt, tm); +} +#endif // WIN32 + + +// Our cached copy of the current time +RTime32 CRTime::sm_nTimeLastSystemTimeUpdate = 0; // initialize to large negative value to trigger immediate FileTimeCur update +char CRTime::sm_rgchLocalTimeCur[16]=""; +char CRTime::sm_rgchLocalDateCur[16]=""; +RTime32 CRTime::sm_nTimeCur = 0; + + + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CRTime::CRTime() +{ + if ( sm_nTimeCur == 0 ) + { + sm_nTimeCur = time(NULL); + } + + m_nStartTime = sm_nTimeCur; + m_bGMT = false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the amount of time that's passed between our time and the +// current time. +// Output: Time that's passed between our time and the current time +//----------------------------------------------------------------------------- +int CRTime::CSecsPassed() const +{ + return( sm_nTimeCur - m_nStartTime ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Updates our current time value. We only +// update the time once per frame-- the rest of the time, we just +// access a cached copy of the time. +// NOTE: This should only be called once per frame. +//----------------------------------------------------------------------------- +void CRTime::UpdateRealTime() +{ + // BUGBUG Alfred: update this less often than once per frame? + RTime32 nTimePrev = sm_nTimeCur; + sm_nTimeCur = time(NULL); + + if ( sm_nTimeCur < nTimePrev ) + { + // time can go backwards sometimes if clock sync adjusts system time; warn when this happens + EmitInfo( SPEW_SYSTEM_MISC, SPEW_ALWAYS, LOG_ALWAYS, "Warning: system time went backward by %d seconds\n", ( nTimePrev - sm_nTimeCur ) ); + } + + // update our time from file time once per second + if ( sm_nTimeCur - sm_nTimeLastSystemTimeUpdate >= 1 ) + { +#ifdef _WIN32 + // get the local time, generate time & date strings and cache the strings, as we will need these + // frequently for logs. + SYSTEMTIME systemTimeLocal; + GetLocalTime( &systemTimeLocal ); + GetTimeFormat( LOCALE_USER_DEFAULT, 0, &systemTimeLocal, "HH:mm:ss", sm_rgchLocalTimeCur, Q_ARRAYSIZE( sm_rgchLocalTimeCur ) ); + GetDateFormat( LOCALE_USER_DEFAULT, 0, &systemTimeLocal, "MM/dd/yy", sm_rgchLocalDateCur, Q_ARRAYSIZE( sm_rgchLocalDateCur ) ); +#elif defined(POSIX) + time_t now; + time( &now ); + struct tm tmStruct; + struct tm *localTime = Plat_gmtime( &now, &tmStruct ); + strftime( sm_rgchLocalTimeCur, Q_ARRAYSIZE( sm_rgchLocalTimeCur ), "%H:%M:%S", localTime ); + strftime( sm_rgchLocalDateCur, Q_ARRAYSIZE( sm_rgchLocalDateCur ), "%m/%d/%y", localTime ); +#else +#error "Implement me" +#endif + sm_nTimeLastSystemTimeUpdate = sm_nTimeCur; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the system clock on this box to specified world time +// Input: rTime32Current - world time to set +//----------------------------------------------------------------------------- +void CRTime::SetSystemClock( RTime32 rTime32Current ) +{ +#ifdef _WIN32 + FILETIME fileTime; + SYSTEMTIME systemTime = {0}; + // convert from seconds since 1/1/1970 to filetime (100 nanoseconds since 1/1/1601) with this magic formula courtesy of MSDN + uint64 ulTmp = ( ( (uint64) rTime32Current ) * 10 * k_nMillion ) + 116444736000000000; + fileTime.dwLowDateTime = (DWORD) ulTmp; + fileTime.dwHighDateTime = ulTmp >> 32; + + // convert from filetime to system time (note this also does time zone conversion to UTC) + BOOL bRet = FileTimeToSystemTime( &fileTime, &systemTime ); + Assert( bRet ); // should never fail + if ( !bRet ) // but if it does, don't set system clock to garbage + return; + + // set system time in UTC + bRet = SetSystemTime( &systemTime ); + Assert( bRet ); + + // update our cached time + sm_nTimeCur = rTime32Current; +#else + Assert( !"Not implemented" ); +#endif // _WIN32 +} + + +//----------------------------------------------------------------------------- +// Purpose: Renders the time +// Output : ptr to time string +//----------------------------------------------------------------------------- +const char* CRTime::Render( char (&buf)[k_RTimeRenderBufferSize] ) const +{ + return Render( m_nStartTime, buf ); +} + +//----------------------------------------------------------------------------- +// Purpose: Renders the time - static function +// Input : rTime32 - time to render +// Output : ptr to time string +//----------------------------------------------------------------------------- +const char* CRTime::Render( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize] ) +{ + if ( !buf ) + { + Assert( buf ); + return nullptr; + } + + // The return value string contains exactly 26 characters and has the form: Wed Jan 02 02:03:55 1980\n\0 + time_t tTime = rTime32; + char pchTime[32]; + if ( !Plat_ctime( &tTime, pchTime, Q_ARRAYSIZE( pchTime ) ) ) + return 0; + + // Remove '\n' + Assert( Q_strlen( pchTime ) == 25 ); + pchTime[ 24 ] = '\0'; + + if ( rTime32 == k_RTime32Infinite ) + Q_strncpy( buf, "Infinite time value", k_RTimeRenderBufferSize ); + else if ( rTime32 == k_RTime32Nil ) + Q_strncpy( buf, "Nil time value", k_RTimeRenderBufferSize ); + else if ( rTime32 < k_RTime32MinValid ) + Q_strncpy( buf, "Invalid time value", k_RTimeRenderBufferSize ); + else + Q_strncpy( buf, pchTime, k_RTimeRenderBufferSize ); + + return buf; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the calendar year (absolute) for the current time +//----------------------------------------------------------------------------- +int CRTime::GetYear() const +{ + time_t timeCur = m_nStartTime; + struct tm tmStruct; + struct tm *ptmCur = m_bGMT ? Plat_gmtime( &timeCur, &tmStruct ) : Plat_localtime( &timeCur, &tmStruct ); + return ptmCur->tm_year + 1900; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the calendar month (0-11) for the current time +//----------------------------------------------------------------------------- +int CRTime::GetMonth() const +{ + time_t timeCur = m_nStartTime; + struct tm tmStruct; + struct tm *ptmCur = m_bGMT ? Plat_gmtime( &timeCur, &tmStruct ) : Plat_localtime( &timeCur, &tmStruct ); + return ptmCur->tm_mon; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the day of the calendar year (0-365) for the current time +//----------------------------------------------------------------------------- +int CRTime::GetDayOfYear() const +{ + time_t timeCur = m_nStartTime; + struct tm tmStruct; + struct tm *ptmCur = m_bGMT ? Plat_gmtime( &timeCur, &tmStruct ) : Plat_localtime( &timeCur, &tmStruct ); + return ptmCur->tm_yday; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the day of the month (1-31) for the current time +//----------------------------------------------------------------------------- +int CRTime::GetDayOfMonth() const +{ + time_t timeCur = m_nStartTime; + struct tm tmStruct; + struct tm *ptmCur = m_bGMT ? Plat_gmtime( &timeCur, &tmStruct ) : Plat_localtime( &timeCur, &tmStruct ); + return ptmCur->tm_mday; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the day of the week (0-6, 0=Sunday) for the current time +//----------------------------------------------------------------------------- +int CRTime::GetDayOfWeek() const +{ + time_t timeCur = m_nStartTime; + struct tm tmStruct; + struct tm *ptmCur = m_bGMT ? Plat_gmtime( &timeCur, &tmStruct ) : Plat_localtime( &timeCur, &tmStruct ); + return ptmCur->tm_wday; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the current hour (0-23) +//----------------------------------------------------------------------------- +int CRTime::GetHour( ) const +{ + time_t timeCur = m_nStartTime; + struct tm tmStruct; + struct tm *ptmCur = m_bGMT ? Plat_gmtime( &timeCur, &tmStruct ) : Plat_localtime( &timeCur, &tmStruct ); + return ptmCur->tm_hour; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the current minute value (0-59) +//----------------------------------------------------------------------------- +int CRTime::GetMinute( ) const +{ + time_t timeCur = m_nStartTime; + struct tm tmStruct; + struct tm *ptmCur = m_bGMT ? Plat_gmtime( &timeCur, &tmStruct ) : Plat_localtime( &timeCur, &tmStruct ); + return ptmCur->tm_min; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the current second value (0-59) +//----------------------------------------------------------------------------- +int CRTime::GetSecond() const +{ + time_t timeCur = m_nStartTime; + struct tm tmStruct; + struct tm *ptmCur = m_bGMT ? Plat_gmtime( &timeCur, &tmStruct ) : Plat_localtime( &timeCur, &tmStruct ); + return ptmCur->tm_sec; +} + + +//----------------------------------------------------------------------------- +// Purpose: Get the ISO week number +//----------------------------------------------------------------------------- +int CRTime::GetISOWeekOfYear() const +{ + int nDay = GetDayOfYear() - ( 1 + GetDayOfWeek() ); + int nISOWeek = nDay / 7; + return nISOWeek; +} + + +//----------------------------------------------------------------------------- +// Purpose: let me know if this is a leap year or not +//----------------------------------------------------------------------------- +/* static */ bool CRTime::BIsLeapYear( int nYear ) +{ + // every for years, unless it is a century; or if it is every 4th century + if ( ( nYear % 4 == 0 && nYear % 100 != 0) || nYear % 400 == 0) + return true; /* leap */ + else + return false; /* no leap */ +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to given sting +// Using a format string to convert +// Input: pchFmt - Format string that describes how to parse the value +// YY or YYYY is year, MM month, DD day of the month, +// hh mm ss is hour minute second. +// Z0000 is a time-zone offset, eg -0700. +// Everything except YY is optional (will be considered 0 if not given) +// pchValue - String containing the value to covert +// Output: RTime32 +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32FromFmtString( const char *pchFmt, const char* pchValue ) +{ + struct tm tm; + + char rgchNum[8]; + char rgchValue[64]; + + Q_memset( &tm, 0x0, sizeof( tm ) ); + Q_strncpy( rgchValue, pchValue, sizeof( rgchValue) ); + + int cchFmt = Q_strlen( pchFmt ); + int cchValue = Q_strlen( rgchValue ); + if ( cchFmt != cchValue || cchFmt < 4 ) + { + Assert( false ); + return k_RTime32Nil; + } + + const char *pchYYYY = Q_strstr( pchFmt, "YYYY" ); + const char *pchYY = Q_strstr( pchFmt, "YY" ); + const char *pchMM = Q_strstr( pchFmt, "MM" ); + const char *pchMnt = Q_strstr( pchFmt, "Mnt" ); + const char *pchDD = Q_strstr( pchFmt, "DD" ); + const char *pchThh = Q_strstr( pchFmt, "hh" ); + const char *pchTmm = Q_strstr( pchFmt, "mm" ); + const char *pchTss = Q_strstr( pchFmt, "ss" ); + const char *pchTzone = Q_strstr( pchFmt, "Z0000" ); + + if ( pchYYYY ) + { + pchYYYY = rgchValue + ( pchYYYY - pchFmt ); + Q_strncpy( rgchNum, pchYYYY, 5 ); + tm.tm_year = strtol( rgchNum, 0, 10 ) - 1900; + } + else if ( pchYY ) + { + pchYY = rgchValue + ( pchYY - pchFmt ); + Q_strncpy( rgchNum, pchYY, 3 ); + tm.tm_year = strtol( rgchNum, 0, 10 ) + 100; + } + else + return k_RTime32Nil; // must have a year + + if ( pchMM ) + { + pchMM = rgchValue + ( pchMM - pchFmt ); + Q_strncpy( rgchNum, pchMM, 3 ); + tm.tm_mon = strtol( rgchNum, 0, 10 ) - 1; + } + if ( pchMnt ) + { + static const char *rgszMonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + pchMnt = rgchValue + ( pchMnt - pchFmt ); + int i; + for ( i = 0; i < 12; i++ ) + { + if ( !V_strnicmp( rgszMonthNames[i], pchMnt, 3 ) ) + break; + } + if ( i < 12 ) + tm.tm_mon = i; + } + if ( pchDD ) + { + pchDD = rgchValue + (pchDD - pchFmt ); + Q_strncpy( rgchNum, pchDD, 3 ); + tm.tm_mday = strtol( rgchNum, 0, 10 ); + } + if ( pchThh ) + { + pchThh = rgchValue + ( pchThh - pchFmt ); + Q_strncpy( rgchNum, pchThh, 3 ); + tm.tm_hour = strtol( rgchNum, 0, 10 ); + } + if ( pchTmm ) + { + pchTmm = rgchValue + ( pchTmm - pchFmt ); + Q_strncpy( rgchNum, pchTmm, 3 ); + tm.tm_min = strtol( rgchNum, 0, 10 ); + } + if ( pchTss ) + { + pchTss = rgchValue + (pchTss - pchFmt ); + Q_strncpy( rgchNum, pchTss, 3 ); + tm.tm_sec = strtol( rgchNum, 0, 10 ); + } + if ( pchTzone ) + { + long nOffset = 0; + pchTzone = rgchValue + (pchTzone - pchFmt); + Q_strncpy( rgchNum, pchTzone, 6 ); + nOffset = strtol( rgchNum, 0, 10 ); + tm.tm_hour -= nOffset / 100; // to go from -0700 to UTC, need to ADD seven + + // is this a sub-hour timezone? eg +0545 Kathmandu + int nMinutesOffset = nOffset % 100; + if ( nMinutesOffset ) + tm.tm_min -= nMinutesOffset; + + // OK, so this is somewhat lame: mktime assumes our tm units are in LOCAL time. + // However, we have just created a UTC time by using the supplied timezone offset. + // The rational thing to do here would be to call mkgmtime() instead of mktime(), + // but that function isn't available in unix-land. + // SO, instead we will MANUALLY convert this tm back to local time + +#if ( defined( _MSC_VER ) && _MSC_VER >= 1900 ) + #define timezone _timezone + #define daylight _daylight +#endif + + // subtract timezone, which is in SECONDS. timezone is (UTC - local), so local = UTC - timezone + tm.tm_sec -= timezone; + // timezone does NOT account for DST, so if we are in DST, we need to ADD an hour. + // This is because the value of timezone we subtracted was one hour TOO LARGE + tm.tm_hour += daylight ? 1 : 0; + + } + + // We don't know if DST is in effect, let the CRT + // figure it out + tm.tm_isdst = -1; + + return mktime( &tm ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to given sting which is +// expected to be in one of the common HTTP date formats. +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32FromHTTPDateString( const char* pchValue ) +{ + // First format here is RFC 822/1123 format + struct tm tm; + if ( strptime( pchValue, "%a, %e %b %Y %H:%M:%S", &tm ) ) + { + return Plat_timegm( &tm ); + } + + // If that failed, try RFC 850/1036 format + if ( strptime( pchValue, "%a, %e-%b-%y %H:%M:%S", &tm ) ) + { + return Plat_timegm( &tm ); + } + + // If that also failed, give up + return k_RTime32Nil; +} + + +//----------------------------------------------------------------------------- +// Purpose: Parse time from string RFC3339 format (assumes UTC) +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32FromRFC3339UTCString( const char* pchValue ) +{ + + // UTC only from RFC 3339. Should be 2005-05-15T17:11:51Z + struct tm tm; + if ( strptime( pchValue, "%Y-%m-%dT%H:%M:%SZ", &tm ) ) + { + return Plat_timegm( &tm ); + } + + // If that also failed, give up + return k_RTime32Nil; + +} + + +//----------------------------------------------------------------------------- +// Purpose: Output time in RFC3339 format (assumes UTC) +//----------------------------------------------------------------------------- +// STATIC +const char* CRTime::RTime32ToRFC3339UTCString( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize] ) +{ + if ( !buf ) + { + Assert( buf ); + return nullptr; + } + + // Store the result in a temporary buffer, so that you can use several in a single printf. + time_t tTime = rTime32; + struct tm tmStruct; + struct tm *ptm = Plat_gmtime( &tTime, &tmStruct ); + + if ( rTime32 == k_RTime32Nil || !ptm ) + return "NIL"; + + if ( rTime32 == k_RTime32Infinite ) + return "Infinite time value"; + + if ( rTime32 < k_RTime32MinValid || !ptm ) + return "Invalid time value"; + + Q_snprintf( buf, k_RTimeRenderBufferSize, "%04u-%02u-%02uT%02u:%02u:%02uZ", ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec ); + return buf; +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to given sting +// "YYYY-MM-DD hh:mm:ss" (copied from sqlhelpers.cpp) +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32FromString( const char* pszValue ) +{ + struct tm tm; + + char num[5]; + char szValue[64]; + + Q_memset( &tm, 0x0, sizeof( tm ) ); + Q_strncpy( szValue, pszValue, sizeof( szValue) ); + + const char *str= szValue; + + num[0] =*str++; num[1] =*str++; num[2] =*str++; num[3] =*str++; num[4] = 0; + tm.tm_year = strtol( num, 0, 10 ) - 1900; + if (*str == '-') str++; + num[0] = *str++; num[1] = *str++; num[2] = 0; + tm.tm_mon = strtol( num, 0, 10 ) - 1; + if (*str == '-') str++; + num[0] = *str++; num[1] = *str++; num[2] = 0; + tm.tm_mday = strtol( num, 0, 10 ); + + if ( *str != 0 ) + { + // skip an optional space or T between date and time + if ( *str == ' ' || *str == 'T' ) + str++; + + // time is given too + num[0] = *str++; num[1] = *str++; num[2] = 0; + tm.tm_hour = strtol( num, 0, 10 ); + if (*str == ':') str++; + num[0] = *str++; num[1] = *str++; num[2] = 0; + tm.tm_min = strtol( num, 0, 10 ); + if (*str == ':') str++; + num[0] = *str++; num[1] = *str++; num[2] = 0; + tm.tm_sec = strtol( num, 0, 10 ); + } + tm.tm_isdst = -1; + + return mktime( &tm ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a static string "YYYY-MM-DD hh:mm:ss" for given RTime32 +// Input: rTime32 - +// bNoPunct - No dashes, colons or spaces will be in the output string +// bOnlyDate - Only output the date +// Output: const char * -- only usable till the next yield +//----------------------------------------------------------------------------- +// STATIC +const char* CRTime::RTime32ToString( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize], bool bNoPunct /*=false*/, bool bOnlyDate /*= false*/ ) +{ + if ( !buf ) + { + return nullptr; + } + + // Store the result in a temporary buffer, so that you can use several in a single printf. + time_t tTime = rTime32; + struct tm tmStruct; + struct tm *ptm = Plat_localtime( &tTime, &tmStruct ); + + const char *pchOnlyDateFmt = bNoPunct ? "%04u%02u%02u" : "%04u-%02u-%02u"; + const char *pchDateTimeFmt = bNoPunct ? "%04u%02u%02u%02u%02u%02u" : "%04u-%02u-%02u %02u:%02u:%02u"; + if ( rTime32 == k_RTime32Nil || !ptm ) + return "NIL"; + + if ( rTime32 == k_RTime32Infinite ) + return "Infinite time value"; + + if ( rTime32 < k_RTime32MinValid || !ptm ) + return "Invalid time value"; + + if ( bOnlyDate ) + { + Q_snprintf( buf, k_RTimeRenderBufferSize, pchOnlyDateFmt, + ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday ); + } + else + { + Q_snprintf( buf, k_RTimeRenderBufferSize, pchDateTimeFmt, + ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, + ptm->tm_hour, ptm->tm_min, ptm->tm_sec ); + } + + return buf; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns a static string like "Aug 21" for given RTime32 +// Input: rTime32 - +// +// Output: const char * -- only usable till the next yield +//----------------------------------------------------------------------------- +// STATIC +const char* CRTime::RTime32ToDayString( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize], bool bGMT ) +{ + if ( !buf ) + { + return nullptr; + } + + // Store the result in a temporary buffer, so that you can use several in a single printf. + time_t tTime = rTime32; + struct tm tmStruct; + struct tm *ptm = bGMT ? Plat_gmtime( &tTime, &tmStruct ) : Plat_localtime( &tTime, &tmStruct ); + + DbgVerify( strftime( buf, k_RTimeRenderBufferSize, "%b %d", ptm ) ); + return buf; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to the beginning of +// the day represented by rtime32 +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32BeginningOfDay( const RTime32 rtime32 ) +{ + time_t timeCur = rtime32; + struct tm tmStruct; + struct tm *ptmCur = Plat_localtime( &timeCur, &tmStruct ); + if ( !ptmCur ) + return k_RTime32Nil; + + // midnight + ptmCur->tm_hour = 0; + ptmCur->tm_min = 0; + ptmCur->tm_sec = 0; + + // Let it compute DST + ptmCur->tm_isdst = -1; + + return mktime( ptmCur ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to the beginning of +// the next day after rtime32 +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32BeginningOfNextDay( const RTime32 rtime32 ) +{ + time_t timeCur = rtime32; + struct tm tmStruct; + struct tm *ptmCur = Plat_localtime( &timeCur, &tmStruct ); + if ( !ptmCur ) + return k_RTime32Nil; + + // It will move to the next month etc if need be + ptmCur->tm_mday++; + + // midnight + ptmCur->tm_hour = 0; + ptmCur->tm_min = 0; + ptmCur->tm_sec = 0; + + // Let it compute DST + ptmCur->tm_isdst = -1; + + return mktime( ptmCur ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to the first day of +// the month indicated by rtime32 +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32FirstDayOfMonth( const RTime32 rtime32 ) +{ + time_t timeCur = rtime32; + struct tm tmStruct; + struct tm *ptmCur = Plat_localtime( &timeCur, &tmStruct ); + if ( !ptmCur ) + return k_RTime32Nil; + + // first day of month + ptmCur->tm_mday = 1; + + // midnight + ptmCur->tm_hour = 0; + ptmCur->tm_min = 0; + ptmCur->tm_sec = 0; + + // Let it compute DST + ptmCur->tm_isdst = -1; + + return mktime( ptmCur ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to the last day of +// the month indicated by rtime32 +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32LastDayOfMonth( const RTime32 rtime32 ) +{ + time_t timeCur = rtime32; + struct tm tmStruct; + struct tm *ptmCur = Plat_localtime( &timeCur, &tmStruct ); + if ( !ptmCur ) + return k_RTime32Nil; + + // Zeroth day of month N becomes last day of month (N-1) + ptmCur->tm_mon++; + ptmCur->tm_mday = 0; + + // midnight + ptmCur->tm_hour = 0; + ptmCur->tm_min = 0; + ptmCur->tm_sec = 0; + + // Let it compute DST + ptmCur->tm_isdst = -1; + + return mktime( ptmCur ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to the first day of +// the month after the one indicated by rtime32 +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32FirstDayOfNextMonth( const RTime32 rtime32 ) +{ + time_t timeCur = rtime32; + struct tm tmStruct; + struct tm *ptmCur = Plat_localtime( &timeCur, &tmStruct ); + if ( !ptmCur ) + return k_RTime32Nil; + + ptmCur->tm_mon++; + ptmCur->tm_mday = 1; + + // midnight + ptmCur->tm_hour = 0; + ptmCur->tm_min = 0; + ptmCur->tm_sec = 0; + + // Let it compute DST + ptmCur->tm_isdst = -1; + + return mktime( ptmCur ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to the last day of +// the month after the one indicated by rtime32 +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32LastDayOfNextMonth( const RTime32 rtime32 ) +{ + time_t timeCur = rtime32; + struct tm tmStruct; + struct tm *ptmCur = Plat_localtime( &timeCur, &tmStruct ); + if ( !ptmCur ) + return k_RTime32Nil; + + // use zeroth-day trick - skip 2 months then back a day + ptmCur->tm_mon += 2; + ptmCur->tm_mday = 0; + + // midnight + ptmCur->tm_hour = 0; + ptmCur->tm_min = 0; + ptmCur->tm_sec = 0; + + // Let it compute DST + ptmCur->tm_isdst = -1; + + return mktime( ptmCur ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Calculate and return a time value corresponding to the Nth day of +// the month. If that month only has K days, K < N, it will return +// the Kth day. The input should be reasonable (don't ask for the -5th +// day of the month). +// +// Input: rtime32 - Time representing some time in the month interested in +// nDay - The day of that month you want the return to be set to +// +// Return: Time value equal to midnight on that day. +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32NthDayOfMonth( const RTime32 rtime32, int nDay ) +{ + Assert( nDay > 0 ); + Assert( nDay < 32 ); + + time_t timeCur = rtime32; + struct tm tmStruct; + struct tm *ptmCur = Plat_localtime( &timeCur, &tmStruct ); + if ( !ptmCur ) + return k_RTime32Nil; + + int nCurMonth = ptmCur->tm_mon; + + ptmCur->tm_mday = nDay; + + // midnight + ptmCur->tm_hour = 0; + ptmCur->tm_min = 0; + ptmCur->tm_sec = 0; + + // Let it compute DST + ptmCur->tm_isdst = -1; + + // This call will modify ptmCur in-place + time_t timeThen = mktime( ptmCur ); + + // See if the month changed + if ( ptmCur->tm_mon != nCurMonth ) + { + // use zeroth-day trick to just get the last day of this month + ptmCur->tm_mday = 0; + // Let it compute DST + ptmCur->tm_isdst = -1; + timeThen = mktime( ptmCur ); + } + + return timeThen; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add X months to the current date, and return the Nth day of that +// month. +// +// Input: nNthDayOfMonth - Day of the target month to return a date for +// rtime32StartDate - Time value to add X months to +// nMonthsToAdd - X +// +// Return: Time value equal to midnight on that day. +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32MonthAddChooseDay( int nNthDayOfMonth, RTime32 rtime32StartDate, int nMonthsToAdd ) +{ + // Get the first day of start month + RTime32 rtime32FirstDayOfStartMonth = CRTime( rtime32StartDate ).GetFirstDayOfMonth(); + + // Add X months to that - guaranteed to be correct month + RTime32 rtime32FirstDayOfTargetMonth = CRTime::RTime32DateAdd( rtime32FirstDayOfStartMonth, nMonthsToAdd, k_ETimeUnitMonth ); + + // Then get the Nth day of that month + RTime32 rtime32Target = CRTime::RTime32NthDayOfMonth( rtime32FirstDayOfTargetMonth, nNthDayOfMonth ); + + return rtime32Target; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add or subtract N units of time from the current value. +// Units may be days, weeks, seconds, etc +// Input: rtime32 - Reference time +// nAmount - Number of units to add (neg for subtract) +// eTimeFlagAmountType - Indicates what units are on nAmount +// +// Return: The newly calculated offset time (the input is unmodified) +//----------------------------------------------------------------------------- +// STATIC +RTime32 CRTime::RTime32DateAdd( const RTime32 rtime32, int nAmount, ETimeUnit eTimeAmountType ) +{ + time_t timeCur = rtime32; + struct tm tmStruct; + struct tm *ptmCur = Plat_localtime( &timeCur, &tmStruct ); + if ( !ptmCur ) + return k_RTime32Nil; + + // mktime() is smart enough to take day-of-month values that are out of range and adjust + // everything to make sense. So you can go back 3 weeks by just subtracting 21 from tm_mday. + switch ( eTimeAmountType ) + { + default: + AssertMsg( false, "Bad flag in RTime32DateAdd" ); + break; + case k_ETimeUnitForever: + return k_RTime32Infinite; + case k_ETimeUnitYear: + ptmCur->tm_year += nAmount; + break; + case k_ETimeUnitMonth: + ptmCur->tm_mon += nAmount; + break; + case k_ETimeUnitWeek: + ptmCur->tm_mday += 7 * nAmount; + break; + case k_ETimeUnitDay: + ptmCur->tm_mday += nAmount; + break; + case k_ETimeUnitHour: + ptmCur->tm_hour += nAmount; + break; + case k_ETimeUnitMinute: + ptmCur->tm_min += nAmount; + break; + case k_ETimeUnitSecond: + ptmCur->tm_sec += nAmount; + break; + } + + // Let it compute DST + ptmCur->tm_isdst = -1; + + return mktime( ptmCur ); +} + +//----------------------------------------------------------------------------- +// Purpose: Compare two times and evaluate what calendar boundaries have +// been crossed (eg day, month, hour) between the two times. +// +// Note: in general, the crossing of a large boundary will be accompanied +// by the crossing of all smaller boundaries. The exception is Week: +// the Week boundary is from Saturday to Sunday, and it is possible to +// go over a Month or Year boundary without beginning a new week. +// +// So, the return value is the largest time boundary that was crossed. +// However, the pbWeekChanged value will be set to indicate if the week +// changed in cases where the return value is Month or Year. +// +// Input: unTime1 - First time value +// unTime2 - Second time value +// pbWeekChanged - Indicates if the Week changed +// +// Return: Largest time boundary crossed +//----------------------------------------------------------------------------- +// STATIC +ETimeUnit CRTime::FindTimeBoundaryCrossings( RTime32 unTime1, RTime32 unTime2, bool *pbWeekChanged ) +{ + time_t time1 = unTime1; + time_t time2 = unTime2; + + // have to cache the first one locally, because it's a global object + struct tm tmStruct1; + struct tm *ptmTime1 = Plat_localtime( &time1, &tmStruct1 ); + if ( !ptmTime1 ) + return k_ETimeUnitForever; + struct tm _tmTime1 = *ptmTime1; + ptmTime1 = &_tmTime1; + struct tm tmStruct2; + struct tm *ptmTime2 = Plat_localtime( &time2, &tmStruct2 ); + if ( !ptmTime2 ) + return k_ETimeUnitForever; + + // Need a little extra logic to find week boundaries + // Find this out first, because it may or may not be true even if a + // month / year boundary was crossed. + *pbWeekChanged = false; + + // If the difference is more than 6 days, we crossed a week boundary + if ( ( ( unTime1 > unTime2 ) && ( ( unTime1 - unTime2 ) > k_cSecondsPerWeek ) ) + || ( ( unTime2 > unTime1 ) && ( ( unTime2 - unTime1 ) > k_cSecondsPerWeek ) ) ) + { + *pbWeekChanged = true; + } + else if ( ptmTime1->tm_yday != ptmTime2->tm_yday ) + { + // Otherwise, we have to look at wday - if the later time + // has a lower or equal wday value, then we crossed a week boundary + if ( unTime2 > unTime1 ) + { + if ( ptmTime2->tm_wday <= ptmTime1->tm_wday ) + *pbWeekChanged = true; + } + else + { + if ( ptmTime1->tm_wday <= ptmTime2->tm_wday ) + *pbWeekChanged = true; + } + } + + // Evaluate larger boundaries first. As soon as we detect + // that we've crossed a boundary, we consider all smaller boundaries + // crossed too. + + // Year + if ( ptmTime1->tm_year != ptmTime2->tm_year ) + return k_ETimeUnitYear; + + // Month + if ( ptmTime1->tm_mon != ptmTime2->tm_mon ) + return k_ETimeUnitMonth; + + // If the week changed, return that now + if ( *pbWeekChanged ) + return k_ETimeUnitWeek; + + // Day + if ( ptmTime1->tm_yday != ptmTime2->tm_yday ) + return k_ETimeUnitDay; + + // Hour + if ( ptmTime1->tm_hour != ptmTime2->tm_hour ) + return k_ETimeUnitHour; + + // If DST changed, make sure that we know an hour boundary was crossed + // (overlap from the "fall back" case may otherwise trick us) + if ( ptmTime1->tm_isdst != ptmTime2->tm_isdst ) + return k_ETimeUnitHour; + + // Minute + if ( ptmTime1->tm_min != ptmTime2->tm_min ) + return k_ETimeUnitMinute; + + // Second + if ( ptmTime1->tm_sec != ptmTime2->tm_sec ) + return k_ETimeUnitSecond; + + return k_ETimeUnitNone; + +} + diff --git a/gcsdk/steamextra/rtime.h b/gcsdk/steamextra/rtime.h new file mode 100644 index 0000000..579cdca --- /dev/null +++ b/gcsdk/steamextra/rtime.h @@ -0,0 +1,187 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Encapsulates real world (wall clock) time +// +//============================================================================= +#ifndef RTIME_H +#define RTIME_H +#ifdef _WIN32 +#pragma once +#endif + + +#ifdef WIN32 +char* strptime(const char *s, const char *format, struct tm *tm); +#endif + +#include <time.h> +#include <ctype.h> +#include <string.h> + + + +class CSTime; + +// Invalid time values +const RTime32 k_RTime32Nil = 0; +// time values between Nil and MinValid are available for special constants +const RTime32 k_RTime32MinValid = 10; +// infinite time value +const RTime32 k_RTime32Infinite = 0x7FFFFFFF; //01-18-2038 + +// Render buffer size +const size_t k_RTimeRenderBufferSize = 25; + +// Flags for component fields. Longer durations must be > shorter ones +// WARNING: DO NOT RENUMBER EXISTING VALUES - STORED IN DATABASE +enum ETimeUnit +{ + k_ETimeUnitNone = 0, + k_ETimeUnitSecond = 1, + k_ETimeUnitMinute = 2, + k_ETimeUnitHour = 3, + k_ETimeUnitDay = 4, + k_ETimeUnitWeek = 5, + k_ETimeUnitMonth = 6, + k_ETimeUnitYear = 7, + k_ETimeUnitForever +}; + +// CRTime +// This is our primary real time structure. +// It offers 1 second resolution beginning on January 1, 1970 (i.e unix time) +// This represents wall clock time +class CRTime +{ +public: + // default constructor initializes to the current time + CRTime(); + CRTime( RTime32 nTime ) : m_nStartTime( nTime ), m_bGMT( false ) {} + + void SetToCurrentTime() { m_nStartTime = sm_nTimeCur; } + void SetFromCurrentTime( int dSecOffset ) { m_nStartTime = sm_nTimeCur + dSecOffset; } + + // Amount of seconds that have passed between this CRTime being set and the current wall clock time. + int CSecsPassed() const; + + // Time accessors + RTime32 GetRTime32() const { return m_nStartTime; } + void SetRTime32( RTime32 rTime32 ) { m_nStartTime = rTime32; } + + // Access our cached current time value + static void UpdateRealTime(); + static void SetSystemClock( RTime32 nCurrentTime ); + static RTime32 RTime32TimeCur() { return sm_nTimeCur; } + + // Render + const char *Render( char (&buf)[k_RTimeRenderBufferSize] ) const; + static const char *Render( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize] ); + + // Return a representation of the current system time + static char *PchTimeCur() { return sm_rgchLocalTimeCur; } + // Return a representation of the current system date + static char *PchDateCur() { return sm_rgchLocalDateCur; } + + // comparisons against other CRTime objects + bool operator==( const CRTime &val ) const { return val.m_nStartTime == m_nStartTime; } + bool operator<( const CRTime &val ) const { return m_nStartTime < val.m_nStartTime; } + bool operator<=( const CRTime &val ) const { return m_nStartTime <= val.m_nStartTime; } + bool operator>( const CRTime &val ) const { return m_nStartTime > val.m_nStartTime; } + bool operator>=( const CRTime &val ) const { return m_nStartTime >= val.m_nStartTime; } + + // comparisons against RTime32 numbers (to avoid implicit construct/destruct of a temporary CRTime) + bool operator==( const RTime32 &val ) const { return m_nStartTime == val; } + bool operator<( const RTime32 &val ) const { return m_nStartTime < val; } + bool operator<=( const RTime32 &val ) const { return m_nStartTime <= val; } + bool operator>( const RTime32 &val ) const { return m_nStartTime > val; } + bool operator>=( const RTime32 &val ) const { return m_nStartTime >= val; } + + const CRTime& operator=( const RTime32 &val ) { m_nStartTime = val; return *this; } + const CRTime& operator=( const CRTime &val ) { m_nStartTime = val.m_nStartTime; return *this; } + const CRTime operator+( const int &nVal ) { return m_nStartTime + nVal; } + // Add exactly X seconds to this time + const CRTime& operator+=( const int &nVal ) { m_nStartTime += nVal; return *this; } + + // Component Details + int GetYear() const; + int GetMonth() const; // returns 0..11 + int GetDayOfYear() const; + int GetDayOfMonth() const; + int GetDayOfWeek() const; + int GetHour() const; + int GetMinute() const; + int GetSecond() const; + int GetISOWeekOfYear() const; + + // Handy references to nearby time boundaries + static RTime32 RTime32BeginningOfDay( const RTime32 ); // at 00:00:00 + static RTime32 RTime32BeginningOfNextDay( const RTime32 ); // at 00:00:00 + static RTime32 RTime32FirstDayOfMonth( const RTime32 ); // at 00:00:00 + static RTime32 RTime32LastDayOfMonth( const RTime32 ); // at 00:00:00 + static RTime32 RTime32FirstDayOfNextMonth( const RTime32 ); // at 00:00:00 + static RTime32 RTime32LastDayOfNextMonth( const RTime32 ); // at 00:00:00 + + static bool BIsLeapYear( int nYear ); + bool BIsLeapYear() const { return CRTime::BIsLeapYear( GetYear() ); } + + // Parse time using a format string with strptime format + static RTime32 RTime32FromFmtString( const char *pchFmt, const char* pchValue ); + + // Parse time from string in standard HTTP date format + static RTime32 RTime32FromHTTPDateString( const char* pchValue ); + + // Parse time from string RFC3339 format (assumes UTC) + static RTime32 RTime32FromRFC3339UTCString( const char* pchValue ); + static const char* RTime32ToRFC3339UTCString( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize] ); + + // parse time from string "YYYY-MM-DD hh:mm:ss" or just "YYYY-MM-DD", the ' ',':','-' are optional + static RTime32 RTime32FromString( const char* pszValue ); + + // turns RTime32 in a string like "YYYY-MM-DD hh:mm:ss" + static const char* RTime32ToString( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize], bool bNoPunct = false, bool bOnlyDate = false ); + + // turns RTime32 into a string like "Aug 21" + static const char* RTime32ToDayString( const RTime32 rTime32, char (&buf)[k_RTimeRenderBufferSize], bool bGMT = false ); + + // If the month only has K days, K < N, returns Kth day + static RTime32 RTime32NthDayOfMonth( const RTime32, int nDay ); // at 00:00:00 + + // Add X months but return the Nth day of that month. If the month only has K days, K < N, returns Kth day. + static RTime32 RTime32MonthAddChooseDay( int nNthDayOfMonth, RTime32 rtime32StartDate, int nMonthsToAdd ); + + RTime32 GetBeginningOfDay() const { return RTime32BeginningOfDay( m_nStartTime ); } + RTime32 GetBeginningOfNextDay() const { return RTime32BeginningOfNextDay( m_nStartTime ); } + RTime32 GetFirstDayOfMonth() const { return RTime32FirstDayOfMonth( m_nStartTime ); } + RTime32 GetLastDayOfMonth() const { return RTime32LastDayOfMonth( m_nStartTime ); } + RTime32 GetFirstDayOfNextMonth() const { return RTime32FirstDayOfNextMonth( m_nStartTime ); } + RTime32 GetLastDayOfNextMonth() const { return RTime32LastDayOfNextMonth( m_nStartTime ); } + RTime32 GetNthDayOfMonth( int nDay ) const { return RTime32NthDayOfMonth( m_nStartTime, nDay ); } + RTime32 MonthAddChooseDay( int nNthDayOfMonth, int nMonthsToAdd ) const { return RTime32MonthAddChooseDay( nNthDayOfMonth, m_nStartTime, nMonthsToAdd ); } + + // Add or subtract N days, weeks, minutes, etc from the current time + static RTime32 RTime32DateAdd( const RTime32, int nAmount, ETimeUnit eTimeAmountType ); + RTime32 DateAdd( int nAmount, ETimeUnit eTimeAmountType ) const { return RTime32DateAdd( m_nStartTime, nAmount, eTimeAmountType ); } + + // Compare two times, and return what the largest time boundary crossed between the two was. + // Week boundaries do not line up with Month or Year boundaries, so you must rely on pbWeekChanged for them! + static ETimeUnit FindTimeBoundaryCrossings( RTime32 unTime1, RTime32 unTime2, bool *pbWeekChanged ); + + void SetToGMT( bool bUseGMT ) { m_bGMT = bUseGMT;} + bool BIsGMT() const { return m_bGMT; } + +private: + // prevent assignment or copy construction from the server time type + const CRTime& operator=( const CSTime &val ) { return *this; } + CRTime( const CSTime& ) {} + + RTime32 m_nStartTime; // the time store by this instance (wall clock, in seconds) + static RTime32 sm_nTimeCur; // current system wide wall clock time + + static char sm_rgchLocalTimeCur[16]; // string version of time for logging + static char sm_rgchLocalDateCur[16]; // string version of date for logging + static RTime32 sm_nTimeLastSystemTimeUpdate; // last time we updated above two logging strings + + bool m_bGMT; +}; + +#endif // RTIME_H diff --git a/gcsdk/steamextra/steam/isteamgamecoordinator.h b/gcsdk/steamextra/steam/isteamgamecoordinator.h new file mode 100644 index 0000000..63f3c1b --- /dev/null +++ b/gcsdk/steamextra/steam/isteamgamecoordinator.h @@ -0,0 +1,64 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: interface to the game coordinator for this application +// +//============================================================================= + +#ifndef ISTEAMGAMECOORDINATOR +#define ISTEAMGAMECOORDINATOR +#ifdef _WIN32 +#pragma once +#endif + +#include "steam/steamtypes.h" +#include "steam/steamclientpublic.h" +#include "steam/isteamclient.h" + +// list of possible return values from the ISteamGameCoordinator API +enum EGCResults +{ + k_EGCResultOK = 0, + k_EGCResultNoMessage = 1, // There is no message in the queue + k_EGCResultBufferTooSmall = 2, // The buffer is too small for the requested message + k_EGCResultNotLoggedOn = 3, // The client is not logged onto Steam + k_EGCResultInvalidMessage = 4, // Something was wrong with the message being sent with SendMessage +}; + + +//----------------------------------------------------------------------------- +// Purpose: Functions for sending and receiving messages from the Game Coordinator +// for this application +//----------------------------------------------------------------------------- +class ISteamGameCoordinator +{ +public: + + // sends a message to the Game Coordinator + virtual EGCResults SendMessage( uint32 unMsgType, const void *pubData, uint32 cubData ) = 0; + + // returns true if there is a message waiting from the game coordinator + virtual bool IsMessageAvailable( uint32 *pcubMsgSize ) = 0; + + // fills the provided buffer with the first message in the queue and returns k_EGCResultOK or + // returns k_EGCResultNoMessage if there is no message waiting. pcubMsgSize is filled with the message size. + // If the provided buffer is not large enough to fit the entire message, k_EGCResultBufferTooSmall is returned + // and the message remains at the head of the queue. + virtual EGCResults RetrieveMessage( uint32 *punMsgType, void *pubDest, uint32 cubDest, uint32 *pcubMsgSize ) = 0; + +}; +#define STEAMGAMECOORDINATOR_INTERFACE_VERSION "SteamGameCoordinator001" + +// callback notification - A new message is available for reading from the message queue +struct GCMessageAvailable_t +{ + enum { k_iCallback = k_iSteamGameCoordinatorCallbacks + 1 }; + uint32 m_nMessageSize; +}; + +// callback notification - A message failed to make it to the GC. It may be down temporarily +struct GCMessageFailed_t +{ + enum { k_iCallback = k_iSteamGameCoordinatorCallbacks + 2 }; +}; + +#endif // ISTEAMGAMECOORDINATOR diff --git a/gcsdk/steamextra/steamid.cpp b/gcsdk/steamextra/steamid.cpp new file mode 100644 index 0000000..6a4eb01 --- /dev/null +++ b/gcsdk/steamextra/steamid.cpp @@ -0,0 +1,729 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifdef TF +#include "steamcommon.h" +#define INCLUDED_STEAM2_USERID_STRUCTS +#include "steam/steamclientpublic.h" +#endif + +#include "stdafx.h" +#ifdef HL1 +#include "steamcommon.h" +#include "steam/steamclientpublic.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifndef UINT64_MAX +#define UINT64_MAX ((uint64)-1) +#endif + +static const char *DecimalToUint64( const char *pchStr, uint64 unLimit, + uint64 *punVal ) +{ + const char *pchStart = pchStr; + uint64 unVal = 0; + + while ( *pchStr >= '0' && *pchStr <= '9' ) + { + uint64 unNext = unVal * 10; + + if ( unNext < unVal ) + { + // 64-bit overflow. + return NULL; + } + + unVal = unNext + (uint64)( *pchStr - '0' ); + if ( unVal > unLimit ) + { + // Limit overflow. + return NULL; + } + + pchStr++; + } + if ( pchStr == pchStart ) + { + // No number at all. + return NULL; + } + + *punVal = unVal; + return pchStr; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : pchSteamID - text representation of a Steam ID +//----------------------------------------------------------------------------- +CSteamID::CSteamID( const char *pchSteamID, EUniverse eDefaultUniverse /* = k_EUniverseInvalid */ ) +{ + SetFromString( pchSteamID, eDefaultUniverse ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Initializes a steam ID from a string +// Input : pchSteamID - text representation of a Steam ID +//----------------------------------------------------------------------------- +void CSteamID::SetFromString( const char *pchSteamID, EUniverse eDefaultUniverse ) +{ + uint nAccountID = 0; + uint nInstance = 1; + EUniverse eUniverse = eDefaultUniverse; + EAccountType eAccountType = k_EAccountTypeIndividual; +#ifdef DBGFLAG_ASSERT + // TF Merge -- Assert is debug-only and we have unused variable warnings on :-/ + const char *pchSteamIDString = pchSteamID; +#endif + CSteamID StrictID; + + StrictID.SetFromStringStrict( pchSteamID, eDefaultUniverse ); + + if ( *pchSteamID == '[' ) + pchSteamID++; + + // BUGBUG Rich use the Q_ functions + if (*pchSteamID == 'A') + { + // This is test only + pchSteamID++; // skip the A + eAccountType = k_EAccountTypeAnonGameServer; + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + + if ( strchr( pchSteamID, '(' ) ) + sscanf( strchr( pchSteamID, '(' ), "(%u)", &nInstance ); + const char *pchColon = strchr( pchSteamID, ':' ); + if ( pchColon && *pchColon != 0 && strchr( pchColon+1, ':' )) + { + sscanf( pchSteamID, "%u:%u:%u", (uint*)&eUniverse, &nAccountID, &nInstance ); + } + else if ( pchColon ) + { + sscanf( pchSteamID, "%u:%u", (uint*)&eUniverse, &nAccountID ); + } + else + { + sscanf( pchSteamID, "%u", &nAccountID ); + } + + if ( nAccountID == 0 ) + { + // i dont care what number you entered + CreateBlankAnonLogon(eUniverse); + } + else + { + InstancedSet( nAccountID, nInstance, eUniverse, eAccountType ); + } + // Catch cases where we're allowing sloppy input that we + // might not want to allow. + AssertMsg1( this->operator==( StrictID ), "Steam ID does not pass strict parsing: '%s'", pchSteamIDString ); + return; + } + else if (*pchSteamID == 'G') + { + pchSteamID++; // skip the G + eAccountType = k_EAccountTypeGameServer; + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + } + else if (*pchSteamID == 'C') + { + pchSteamID++; // skip the C + eAccountType = k_EAccountTypeContentServer; + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + } + else if (*pchSteamID == 'g') + { + pchSteamID++; // skip the g + eAccountType = k_EAccountTypeClan; + nInstance = 0; + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + } + else if (*pchSteamID == 'c') + { + pchSteamID++; // skip the c + eAccountType = k_EAccountTypeChat; + nInstance = k_EChatInstanceFlagClan; + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + } + else if (*pchSteamID == 'L') + { + pchSteamID++; // skip the c + eAccountType = k_EAccountTypeChat; + nInstance = k_EChatInstanceFlagLobby; + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + } + else if (*pchSteamID == 'T') + { + pchSteamID++; // skip the T + eAccountType = k_EAccountTypeChat; + nInstance = 0; // Anon chat + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + } + else if (*pchSteamID == 'U') + { + pchSteamID++; // skip the U + eAccountType = k_EAccountTypeIndividual; + nInstance = 1; + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + } + else if (*pchSteamID == 'i') + { + pchSteamID++; // skip the i + eAccountType = k_EAccountTypeInvalid; + nInstance = 1; + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + } + + if ( strchr( pchSteamID, ':' ) ) + { + if (*pchSteamID == '[') + pchSteamID++; // skip the optional [ + sscanf( pchSteamID, "%u:%u", (uint*)&eUniverse, &nAccountID ); + if ( eUniverse == k_EUniverseInvalid ) + eUniverse = eDefaultUniverse; + } + else + { + uint64 unVal64 = 0; + + sscanf( pchSteamID, "%llu", &unVal64 ); + if ( unVal64 > UINT_MAX ) + { + // Assume a full 64-bit Steam ID. + SetFromUint64( unVal64 ); + // Catch cases where we're allowing sloppy input that we + // might not want to allow. + AssertMsg1( this->operator==( StrictID ), "Steam ID does not pass strict parsing: '%s'", pchSteamIDString ); + return; + } + else + { + nAccountID = (uint)unVal64; + } + } + + Assert( (eUniverse > k_EUniverseInvalid) && (eUniverse < k_EUniverseMax) ); + + InstancedSet( nAccountID, nInstance, eUniverse, eAccountType ); + + // Catch cases where we're allowing sloppy input that we + // might not want to allow. + AssertMsg1( this->operator==( StrictID ), "Steam ID does not pass strict parsing: '%s'", pchSteamIDString ); +} + +// SetFromString allows many partially-correct strings, constraining how +// we might be able to change things in the future. +// SetFromStringStrict requires the exact string forms that we support +// and is preferred when the caller knows it's safe to be strict. +// Returns whether the string parsed correctly. The ID may +// still be invalid even if the string parsed correctly. +// If the string didn't parse correctly the ID will always be invalid. +bool CSteamID::SetFromStringStrict( const char *pchSteamID, EUniverse eDefaultUniverse ) +{ + uint nAccountID = 0; + uint nInstance = 1; + uint unMaxVal = 2; + EUniverse eUniverse = eDefaultUniverse; + EAccountType eAccountType = k_EAccountTypeIndividual; + char chPrefix; + bool bBracket = false; + bool bValid = true; + uint64 unVal[3]; + const char *pchEnd; + + // Start invalid. + Clear(); + + if ( !pchSteamID ) + { + return false; + } + + if ( *pchSteamID == '[' ) + { + pchSteamID++; + bBracket = true; + } + + chPrefix = *pchSteamID; + switch( chPrefix ) + { + case 'A': + // This is test only + eAccountType = k_EAccountTypeAnonGameServer; + unMaxVal = 3; + break; + + case 'G': + eAccountType = k_EAccountTypeGameServer; + break; + + case 'C': + eAccountType = k_EAccountTypeContentServer; + break; + + case 'g': + eAccountType = k_EAccountTypeClan; + nInstance = 0; + break; + + case 'c': + eAccountType = k_EAccountTypeChat; + nInstance = k_EChatInstanceFlagClan; + break; + + case 'L': + eAccountType = k_EAccountTypeChat; + nInstance = k_EChatInstanceFlagLobby; + break; + + case 'T': + eAccountType = k_EAccountTypeChat; + nInstance = 0; // Anon chat + break; + + case 'U': + eAccountType = k_EAccountTypeIndividual; + nInstance = 1; + break; + + case 'i': + eAccountType = k_EAccountTypeInvalid; + nInstance = 1; + break; + + default: + // We're reserving other leading characters so + // this should only be the plain-digits case. + if (chPrefix < '0' || chPrefix > '9') + { + bValid = false; + } + chPrefix = 0; + break; + } + + if ( chPrefix ) + { + pchSteamID++; // skip the prefix + if (*pchSteamID == '-' || *pchSteamID == ':') + pchSteamID++; // skip the optional - or : + } + + uint unIdx = 0; + + for (;;) + { + pchEnd = DecimalToUint64( pchSteamID, UINT64_MAX, &unVal[unIdx] ); + if ( !pchEnd ) + { + bValid = false; + break; + } + + unIdx++; + + // For 'A' we can have a trailing instance, which must + // be the end of the string. + if ( *pchEnd == '(' && + chPrefix == 'A' ) + { + if ( unIdx > 2 ) + { + // Two instance IDs provided. + bValid = false; + } + + pchEnd = DecimalToUint64( pchEnd + 1, k_unSteamAccountInstanceMask, &unVal[2] ); + if ( !pchEnd || + *pchEnd != ')' ) + { + bValid = false; + break; + } + else + { + nInstance = (uint)unVal[2]; + + pchEnd++; + if ( *pchEnd == ':' ) + { + // Not expecting more values. + bValid = false; + break; + } + } + } + + if ( *pchEnd != ':' ) + { + if ( bBracket ) + { + if ( *pchEnd != ']' || + *(pchEnd + 1) != 0 ) + { + bValid = false; + } + } + else if ( *pchEnd != 0 ) + { + bValid = false; + } + + break; + } + + if ( unIdx >= unMaxVal ) + { + bValid = false; + break; + } + + pchSteamID = pchEnd + 1; + } + + if ( unIdx > 2 ) + { + if ( unVal[2] <= k_unSteamAccountInstanceMask ) + { + nInstance = (uint)unVal[2]; + } + else + { + bValid = false; + } + } + if ( unIdx > 1 ) + { + if ( unVal[0] >= k_EUniverseInvalid && + unVal[0] < k_EUniverseMax ) + { + eUniverse = (EUniverse)unVal[0]; + if ( eUniverse == k_EUniverseInvalid ) + eUniverse = eDefaultUniverse; + } + else + { + bValid = false; + } + + if ( unVal[1] <= k_unSteamAccountIDMask ) + { + nAccountID = (uint)unVal[1]; + } + else + { + bValid = false; + } + } + else if ( unIdx > 0 ) + { + if ( unVal[0] <= k_unSteamAccountIDMask ) + { + nAccountID = (uint)unVal[0]; + } + else if ( !chPrefix ) + { + if ( bValid ) + { + SetFromUint64( unVal[0] ); + } + return bValid; + } + else + { + bValid = false; + } + } + else + { + bValid = false; + } + + if ( bValid ) + { + if ( chPrefix == 'A' ) + { + if ( nAccountID == 0 ) + { + // i dont care what number you entered + CreateBlankAnonLogon(eUniverse); + return bValid; + } + } + + InstancedSet( nAccountID, nInstance, eUniverse, eAccountType ); + } + + return bValid; +} + + +#if defined( INCLUDED_STEAM2_USERID_STRUCTS ) +//----------------------------------------------------------------------------- +// Purpose: Initializes a steam ID from a Steam2 ID string +// Input: pchSteam2ID - Steam2 ID (as a string #:#:#) to convert +// eUniverse - universe this ID belongs to +// Output: true if successful, false otherwise +//----------------------------------------------------------------------------- +bool CSteamID::SetFromSteam2String( const char *pchSteam2ID, EUniverse eUniverse ) +{ + Assert( pchSteam2ID ); + + // Convert the Steam2 ID string to a Steam2 ID structure + TSteamGlobalUserID steam2ID; + steam2ID.m_SteamInstanceID = 0; + steam2ID.m_SteamLocalUserID.Split.High32bits = 0; + steam2ID.m_SteamLocalUserID.Split.Low32bits = 0; + + const char *pchTSteam2ID = pchSteam2ID; + + // Customer support is fond of entering steam IDs in the following form: STEAM_n:x:y + const char *pchOptionalLeadString = "STEAM_"; + if ( V_strnicmp( pchSteam2ID, pchOptionalLeadString, V_strlen( pchOptionalLeadString ) ) == 0 ) + pchTSteam2ID = pchSteam2ID + V_strlen( pchOptionalLeadString ); + + char cExtraCharCheck = 0; + + int cFieldConverted = sscanf( pchTSteam2ID, "%hu:%u:%u%c", &steam2ID.m_SteamInstanceID, + &steam2ID.m_SteamLocalUserID.Split.High32bits, &steam2ID.m_SteamLocalUserID.Split.Low32bits, &cExtraCharCheck ); + + // Validate the conversion ... a special case is steam2 instance ID 1 which is reserved for special DoD handling + if ( cExtraCharCheck != 0 || cFieldConverted == EOF || cFieldConverted < 2 || ( cFieldConverted < 3 && steam2ID.m_SteamInstanceID != 1 ) ) + return false; + + // Now convert to steam ID from the Steam2 ID structure + SetFromSteam2( &steam2ID, eUniverse ); + return true; +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: Renders the steam ID to a buffer. NOTE: for convenience of calling +// code, this code returns a pointer to a static buffer and is NOT thread-safe. +// Output: buffer with rendered Steam ID +//----------------------------------------------------------------------------- +const char * CSteamID::Render() const +{ + // longest length of returned string is k_cBufLen + // [A:%u:%u:%u] + // %u == 10 * 3 + 6 == 36, plus terminator == 37 + const int k_cBufLen = 37; + + const int k_cBufs = 4; // # of static bufs to use (so people can compose output with multiple calls to Render() ) + static char rgchBuf[k_cBufs][k_cBufLen]; + static int nBuf = 0; + char * pchBuf = rgchBuf[nBuf]; // get pointer to current static buf + nBuf ++; // use next buffer for next call to this method + nBuf %= k_cBufs; + + if ( k_EAccountTypeAnonGameServer == m_steamid.m_comp.m_EAccountType ) + { + V_snprintf( pchBuf, k_cBufLen, "[A:%u:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID, m_steamid.m_comp.m_unAccountInstance ); + } + else if ( k_EAccountTypeGameServer == m_steamid.m_comp.m_EAccountType ) + { + V_snprintf( pchBuf, k_cBufLen, "[G:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + else if ( k_EAccountTypeMultiseat == m_steamid.m_comp.m_EAccountType ) + { + V_snprintf( pchBuf, k_cBufLen, "[M:%u:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID, m_steamid.m_comp.m_unAccountInstance ); + } + else if ( k_EAccountTypePending == m_steamid.m_comp.m_EAccountType ) + { + V_snprintf( pchBuf, k_cBufLen, "[P:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + else if ( k_EAccountTypeContentServer == m_steamid.m_comp.m_EAccountType ) + { + V_snprintf( pchBuf, k_cBufLen, "[C:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + else if ( k_EAccountTypeClan == m_steamid.m_comp.m_EAccountType ) + { + // 'g' for "group" + V_snprintf( pchBuf, k_cBufLen, "[g:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + else if ( k_EAccountTypeChat == m_steamid.m_comp.m_EAccountType ) + { + if ( m_steamid.m_comp.m_unAccountInstance & k_EChatInstanceFlagClan ) + { + V_snprintf( pchBuf, k_cBufLen, "[c:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + else if ( m_steamid.m_comp.m_unAccountInstance & k_EChatInstanceFlagLobby ) + { + V_snprintf( pchBuf, k_cBufLen, "[L:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + else // Anon chat + { + V_snprintf( pchBuf, k_cBufLen, "[T:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + } + else if ( k_EAccountTypeInvalid == m_steamid.m_comp.m_EAccountType ) + { + V_snprintf( pchBuf, k_cBufLen, "[I:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + else if ( k_EAccountTypeIndividual == m_steamid.m_comp.m_EAccountType ) + { + if ( m_steamid.m_comp.m_unAccountInstance != k_unSteamUserDesktopInstance ) + V_snprintf( pchBuf, k_cBufLen, "[U:%u:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID, m_steamid.m_comp.m_unAccountInstance ); + else + V_snprintf( pchBuf, k_cBufLen, "[U:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + else if ( k_EAccountTypeAnonUser == m_steamid.m_comp.m_EAccountType ) + { + V_snprintf( pchBuf, k_cBufLen, "[a:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + else + { + V_snprintf( pchBuf, k_cBufLen, "[i:%u:%u]", m_steamid.m_comp.m_EUniverse, m_steamid.m_comp.m_unAccountID ); + } + return pchBuf; +} + + +//----------------------------------------------------------------------------- +// Purpose: Renders the passed-in steam ID to a buffer. NOTE: for convenience of calling +// code, this code returns a pointer to a static buffer and is NOT thread-safe. +// Input: 64-bit representation of Steam ID to render +// Output: buffer with rendered Steam ID +//----------------------------------------------------------------------------- +const char * CSteamID::Render( uint64 ulSteamID ) +{ + CSteamID steamID( ulSteamID ); + return steamID.Render(); +} + + +//----------------------------------------------------------------------------- +// Purpose: some steamIDs are for internal use only +// This is really debug code, but we run with asserts on in retail, so ... +//----------------------------------------------------------------------------- +bool CSteamID::BValidExternalSteamID() const +{ + if ( m_steamid.m_comp.m_EAccountType == k_EAccountTypePending ) + return false; + if ( m_steamid.m_comp.m_EAccountType != k_EAccountTypeAnonGameServer && m_steamid.m_comp.m_EAccountType != k_EAccountTypeContentServer && m_steamid.m_comp.m_EAccountType != k_EAccountTypeAnonUser ) + { + if ( m_steamid.m_comp.m_unAccountID == 0 && m_steamid.m_comp.m_unAccountInstance == 0 ) + return false; + } + return true; +} + +#ifdef STEAM +//----------------------------------------------------------------------------- +// Purpose: Returns the matching chat steamID, with the default instance of 0 +// Input: SteamID, either a Clan or a Chat type +// Output: SteamID with account type changed to chat, and the Clan flag set. +// If account type was not chat to start with, instance will be set to 0 +//----------------------------------------------------------------------------- +CSteamID ChatIDFromSteamID( const CSteamID &steamID ) +{ + if ( steamID.GetEAccountType() == k_EAccountTypeChat ) + return steamID; + + return ChatIDFromClanID( steamID ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the matching chat steamID, with the default instance of 0 +// Input: SteamID, either a Clan type or a Chat type w/ the Clan flag set +// Output: SteamID with account type changed to clan. +// If account type was not clan to start with, instance will be set to 0 +//----------------------------------------------------------------------------- +CSteamID ClanIDFromSteamID( const CSteamID &steamID ) +{ + if ( steamID.GetEAccountType() == k_EAccountTypeClan ) + return steamID; + + return ClanIDFromChatID( steamID ); +} + + +// Asserts steamID type before conversion +CSteamID ChatIDFromClanID( const CSteamID &steamIDClan ) +{ + Assert( steamIDClan.GetEAccountType() == k_EAccountTypeClan ); + + return CSteamID( steamIDClan.GetAccountID(), k_EChatInstanceFlagClan, steamIDClan.GetEUniverse(), k_EAccountTypeChat ); +} + + +// Asserts steamID type before conversion +CSteamID ClanIDFromChatID( const CSteamID &steamIDChat ) +{ + Assert( steamIDChat.GetEAccountType() == k_EAccountTypeChat ); + Assert( k_EChatInstanceFlagClan & steamIDChat.GetUnAccountInstance() ); + + return CSteamID( steamIDChat.GetAccountID(), 0, steamIDChat.GetEUniverse(), k_EAccountTypeClan ); +} + + +//----------------------------------------------------------------------------- +// Purpose: CGameID "hidden" functions +// move these somewhere else maybe +//----------------------------------------------------------------------------- +CGameID::CGameID( const char *pchGameID ) +{ + m_ulGameID = 0; + + sscanf( pchGameID, "%llu", &m_ulGameID ); + + switch ( m_gameID.m_nType ) + { + default: + AssertMsg( false, "Unknown GameID type" ); + m_ulGameID = 0; + break; + case k_EGameIDTypeApp: + case k_EGameIDTypeGameMod: + case k_EGameIDTypeShortcut: + case k_EGameIDTypeP2P: + break; + } +} + + +// renders this Game ID to string +const char * CGameID::Render() const +{ + // longest buffer is log10(2**64) == 20 + 1 == 21 + const int k_cBufLen = 21; + + const int k_cBufs = 4; // # of static bufs to use (so people can compose output with multiple calls to Render() ) + static char rgchBuf[k_cBufs][k_cBufLen]; + static int nBuf = 0; + char * pchBuf = rgchBuf[nBuf]; // get pointer to current static buf + nBuf ++; // use next buffer for next call to this method + nBuf %= k_cBufs; + + V_snprintf( pchBuf, k_cBufLen, "%llu", m_ulGameID ); + + return pchBuf; +} + +// static method to render a uint64 representation of a Game ID to a string +const char * CGameID::Render( uint64 ulGameID ) +{ + CGameID nGameID( ulGameID ); + return nGameID.Render(); +} +#endif diff --git a/gcsdk/steamextra/tier0/t0constants.h b/gcsdk/steamextra/tier0/t0constants.h new file mode 100644 index 0000000..6c1524d --- /dev/null +++ b/gcsdk/steamextra/tier0/t0constants.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: declares a variety of constants +// +// $NoKeywords: $ +//============================================================================= + +#ifndef T0CONSTANTS_H +#define T0CONSTANTS_H +#ifdef _WIN32 +#pragma once +#endif + +//----------------------------------------------------------------------------- +// numeric constants to avoid typos with wrong number of zeros +//----------------------------------------------------------------------------- +const int64 k_nMillion = 1000000; +const int64 k_nThousand = 1000; +const int64 k_nKiloByte = 1024; +const int64 k_nMegabyte = k_nKiloByte * k_nKiloByte; + +//----------------------------------------------------------------------------- +// Timing constants +//----------------------------------------------------------------------------- + +const unsigned int k_nSecondsPerHour = 60*60; +const unsigned int k_nSecondsPerDay = k_nSecondsPerHour * 24; + +const int k_cSecondsPerMinute = 60; +const int k_cSecondsPerHour = k_cSecondsPerMinute * 60; +const int k_cSecondsPerDay = k_cSecondsPerHour * 24; +const int k_cSecondsPerWeek = k_cSecondsPerDay * 7; +const int k_cSecondsPerYear = k_cSecondsPerDay * 365; + +#endif // T0CONSTANTS_H
\ No newline at end of file diff --git a/gcsdk/steamextra/tier1/hashglobals.cpp b/gcsdk/steamextra/tier1/hashglobals.cpp new file mode 100644 index 0000000..775e1da --- /dev/null +++ b/gcsdk/steamextra/tier1/hashglobals.cpp @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + + +#include "stdafx.h" +#include "pearsonshash.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +// This is a table of the values 0-255 in pseudo random order for use in our pearsons hash +// variant implemented below +const unsigned char g_CTHashRandomValues[256] = +{ + 131, 184, 146, 42, 124, 142, 226, 76, 8, 135, 215, 116, 228, 49, 144, 231, + 238, 25, 156, 125, 128, 87, 223, 38, 98, 122, 105, 4, 35, 180, 188, 160, + 179, 59, 218, 0, 192, 207, 209, 150, 227, 143, 140, 161, 73, 84, 111, 162, + 239, 74, 210, 195, 15, 225, 104, 221, 245, 12, 72, 47, 109, 187, 40, 178, + 208, 56, 190, 193, 126, 95, 33, 45, 177, 170, 186, 123, 202, 149, 60, 194, + 168, 102, 71, 148, 46, 121, 52, 119, 196, 247, 127, 145, 75, 79, 61, 254, + 9, 44, 23, 211, 18, 175, 251, 130, 203, 108, 85, 167, 29, 250, 138, 182, + 101, 213, 159, 92, 36, 10, 86, 32, 176, 80, 17, 134, 181, 114, 64, 165, + 89, 68, 6, 14, 205, 137, 117, 7, 39, 132, 26, 19, 214, 99, 166, 163, + 69, 174, 157, 100, 201, 118, 2, 28, 235, 236, 139, 244, 70, 20, 155, 82, + 51, 154, 115, 94, 93, 83, 136, 27, 198, 43, 50, 243, 183, 153, 53, 206, + 77, 55, 57, 3, 220, 147, 253, 110, 37, 246, 97, 13, 120, 103, 91, 169, + 58, 11, 133, 22, 152, 189, 222, 151, 141, 88, 224, 1, 48, 191, 249, 173, + 106, 113, 252, 172, 232, 66, 219, 96, 237, 21, 233, 62, 242, 54, 230, 65, + 78, 248, 16, 41, 31, 200, 90, 112, 255, 171, 164, 24, 199, 81, 212, 197, + 185, 67, 5, 234, 30, 129, 216, 63, 204, 158, 217, 229, 107, 240, 241, 34, +}; diff --git a/gcsdk/steamextra/tier1/murmurhash3.cpp b/gcsdk/steamextra/tier1/murmurhash3.cpp new file mode 100644 index 0000000..72fd8aa --- /dev/null +++ b/gcsdk/steamextra/tier1/murmurhash3.cpp @@ -0,0 +1,74 @@ +//======= Copyright � Valve Corporation, All rights reserved. ================= +// +// Public domain MurmurHash3 by Austin Appleby is a very solid general-purpose +// hash with a 32-bit output. References: +// http://code.google.com/p/smhasher/ (home of MurmurHash3) +// https://sites.google.com/site/murmurhash/avalanche +// http://www.strchr.com/hash_functions +// +//============================================================================= + +#include <stdafx.h> +#include "murmurhash3.h" + +//----------------------------------------------------------------------------- + +uint32 MurmurHash3_32( const void * key, size_t len, uint32 seed, bool bCaselessStringVariant ) +{ + const uint8 * data = (const uint8*)key; + const ptrdiff_t nblocks = len / 4; + uint32 uSourceBitwiseAndMask = 0xDFDFDFDF | ((uint32)bCaselessStringVariant - 1); + + uint32 h1 = seed; + + //---------- + // body + + const uint32 * blocks = (const uint32 *)(data + nblocks*4); + + for(ptrdiff_t i = -nblocks; i; i++) + { + uint32 k1 = LittleDWord(blocks[i]); + k1 &= uSourceBitwiseAndMask; + + k1 *= 0xcc9e2d51; + k1 = (k1 << 15) | (k1 >> 17); + k1 *= 0x1b873593; + + h1 ^= k1; + h1 = (h1 << 13) | (h1 >> 19); + h1 = h1*5+0xe6546b64; + } + + //---------- + // tail + + const uint8 * tail = (const uint8*)(data + nblocks*4); + + uint32 k1 = 0; + + switch(len & 3) + { + case 3: k1 ^= tail[2] << 16; + case 2: k1 ^= tail[1] << 8; + case 1: k1 ^= tail[0]; + k1 &= uSourceBitwiseAndMask; + k1 *= 0xcc9e2d51; + k1 = (k1 << 15) | (k1 >> 17); + k1 *= 0x1b873593; + h1 ^= k1; + }; + + //---------- + // finalization + + h1 ^= len; + + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + return h1; +} diff --git a/gcsdk/steamextra/tier1/murmurhash3.h b/gcsdk/steamextra/tier1/murmurhash3.h new file mode 100644 index 0000000..8f477cd --- /dev/null +++ b/gcsdk/steamextra/tier1/murmurhash3.h @@ -0,0 +1,100 @@ +//======= Copyright � Valve Corporation, All rights reserved. ================= +// +// Public domain MurmurHash3 by Austin Appleby is a very solid general-purpose +// hash with a 32-bit output. References: +// http://code.google.com/p/smhasher/ (home of MurmurHash3) +// https://sites.google.com/site/murmurhash/avalanche +// http://www.strchr.com/hash_functions +// +//============================================================================= + +#ifndef MURMURHASH3_H +#define MURMURHASH3_H + +#if defined(_WIN32) +#pragma once +#endif + +uint32 MurmurHash3_32( const void *key, size_t len, uint32 seed, bool bCaselessStringVariant = false ); + +inline uint32 MurmurHash3String( const char *pszKey, size_t len ) +{ + return MurmurHash3_32( pszKey, len, 1047 /*anything will do for a seed*/, false ); +} + +inline uint32 MurmurHash3StringCaseless( const char *pszKey, size_t len ) +{ + return MurmurHash3_32( pszKey, len, 1047 /*anything will do for a seed*/, true ); +} + +inline uint32 MurmurHash3String( const char *pszKey ) +{ + return MurmurHash3String( pszKey, strlen( pszKey ) ); +} + +inline uint32 MurmurHash3StringCaseless( const char *pszKey ) +{ + return MurmurHash3StringCaseless( pszKey, strlen( pszKey ) ); +} + +template <typename T> +inline uint32 MurmurHash3Item( const T &item ) +{ + return MurmurHash3_32( &item, sizeof(item), 1047 ); +} + +inline uint32 MurmurHash3Int( uint32 h ) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + + +template <> +inline uint32 MurmurHash3Item( const uint32 &item ) +{ + return MurmurHash3Int( item ); +} + +template <> +inline uint32 MurmurHash3Item( const int32 &item ) +{ + return MurmurHash3Int( item ); +} + + +template<typename T> +struct MurmurHash3Functor +{ + typedef uint32 TargetType; + TargetType operator()(const T &key) const + { + return MurmurHash3Item( key ); + } +}; + +template<> +struct MurmurHash3Functor<char *> +{ + typedef uint32 TargetType; + TargetType operator()(const char *key) const + { + return MurmurHash3String( key ); + } +}; + +template<> +struct MurmurHash3Functor<const char *> +{ + typedef uint32 TargetType; + TargetType operator()(const char *key) const + { + return MurmurHash3String( key ); + } +}; + +#endif // MURMURHASH3_H diff --git a/gcsdk/steamextra/tier1/pearsonshash.h b/gcsdk/steamextra/tier1/pearsonshash.h new file mode 100644 index 0000000..29da4a5 --- /dev/null +++ b/gcsdk/steamextra/tier1/pearsonshash.h @@ -0,0 +1,268 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: PearsonsHash.h +// +// This file contains implementation definitions of 'Pearson's' Hash' +// The generic implementation is a template, plus a couple of specializations +// for commonly used types. +// +// Take a look at http://en.wikipedia.org/wiki/Pearson_hashing for more info. +// +//============================================================================= + +#ifndef _PEARSONSHASH_H_ +#define _PEARSONSHASH_H_ + +#if defined(_WIN32) || defined(_WIN64) +#pragma once +#endif + +extern const unsigned char g_CTHashRandomValues[256] ; + +template<typename T> +struct PearsonsHashFunctor +{ + typedef uint32 TargetType ; + TargetType operator()(const T& unKey) const + { + // This is a pearsons hash variant that returns a maximum of 32 bits + size_t size = sizeof(T); + const uint8 * k = (const uint8 *) &unKey; + uint32 byte_one = 0, byte_two = 0, byte_three = 0, byte_four = 0, n; + while (size) + { + --size; + n = *k++; + byte_one = g_CTHashRandomValues[byte_one ^ n]; + + if (size) + { + --size; + n = *k++; + byte_two = g_CTHashRandomValues[byte_two ^ n]; + } + else + break; + + if (size) + { + --size; + n = *k++; + byte_three = g_CTHashRandomValues[byte_three ^ n]; + } + else + break; + + if (size) + { + --size; + n = *k++; + byte_four = g_CTHashRandomValues[byte_four ^ n]; + } + else + break; + } + + TargetType idx = ( byte_four << 24 ) | ( byte_three << 16 ) | ( byte_two << 8 ) | byte_one; + return ( idx ); + } +}; + +// +// We use this specialization for pointer types - it allows somebody +// to define a specialization for some complicated type, and then use a +// pointer to that type as a key, and have that automatically go to the +// specialization for the complicated type ! +// +template<typename T> +struct PearsonsHashFunctor<T*> +{ + typedef uint32 TargetType ; + TargetType operator()(const T* key) const + { + PearsonsHashFunctor<T> functor ; + return functor(*key) ; + } +}; + +// +// This functor specializes for unsigned 32 bit integers, a commonly used type in Steam. +// It should return the exact same result as the unspecialized version on Intel Architecture machines. +// +template<> +struct PearsonsHashFunctor<uint32> +{ + typedef uint32 TargetType ; + TargetType operator()(const uint32 unKey) const + { + uint32 byte_one = g_CTHashRandomValues[(unKey>>0) & 0xff]; + uint32 byte_two = g_CTHashRandomValues[(unKey>>8) & 0xff]; + uint32 byte_three = g_CTHashRandomValues[(unKey>>16) & 0xff]; + uint32 byte_four = g_CTHashRandomValues[(unKey>>24)&0xff]; + return ( byte_four << 24 ) | ( byte_three << 16 ) | ( byte_two << 8 ) | byte_one; + } +}; + +// +// This functor specializes for unsigned 64 bit integers, another commonly used type in Steam. +// It should return the exact same result as the unspecialized version on Intel Architecture machines. +// +template<> +struct PearsonsHashFunctor<uint64> +{ + typedef uint32 TargetType ; + TargetType operator()(const uint64 unKey) const + { + // + // Note that we pull apart the 64 bits in Intel's endian order. + // + uint32 n; + // + // On Intel Machines, to make this return the exact same result as the generic version + // we have to go from least significant byte to most significant byte ! + // + n = static_cast<uint32>((unKey >> (0)) & 0xff) ; + uint32 byte_one = g_CTHashRandomValues[n]; + n = static_cast<uint32>((unKey >> (8)) & 0xff) ; + uint32 byte_two = g_CTHashRandomValues[n]; + n = static_cast<uint32>((unKey >> (16)) & 0xff) ; + uint32 byte_three = g_CTHashRandomValues[n]; + n = static_cast<uint32>((unKey >> (24)) & 0xff) ; + uint32 byte_four = g_CTHashRandomValues[n]; + n = static_cast<uint32>((unKey >> (32)) & 0xff) ; + byte_one = g_CTHashRandomValues[n ^ byte_one]; + n = static_cast<uint32>((unKey >> (8+32)) & 0xff) ; + byte_two = g_CTHashRandomValues[n ^ byte_two]; + n = static_cast<uint32>((unKey >> (16+32)) & 0xff) ; + byte_three = g_CTHashRandomValues[n ^ byte_three]; + n = static_cast<uint32>((unKey >> (24+32)) & 0xff) ; + byte_four = g_CTHashRandomValues[n ^ byte_four]; + return ( byte_four << 24 ) | ( byte_three << 16 ) | ( byte_two << 8 ) | byte_one; + } +}; + +// +// This functor specializes for C standard NULL terminated strings ! +// It is setup so that if you had a char array containing a NULL terminated string +// and correctly sized, ie char rgch[16] = { "123456789012345" } and a +// null terminated string i.e. char *sz = "123456789012345" these will return identical +// results, and both include the NULL terminator in the hash calculation. +// +template<> +struct PearsonsHashFunctor<char*> +{ + typedef uint32 TargetType ; + TargetType operator()(const char* szKey) const + { + const uint8 * k = (const uint8 *) szKey ; + uint32 byte_one = 0, byte_two = 0, byte_three = 0, byte_four = 0, n; + do + { + n = *k++; + byte_one = g_CTHashRandomValues[byte_one ^ n]; + if (n=='\0') + break; + + n = *k++; + byte_two = g_CTHashRandomValues[byte_two ^ n]; + if (n=='\0') + break; + + n = *k++; + byte_three = g_CTHashRandomValues[byte_three ^ n]; + if (n=='\0') + break; + + n = *k++; + byte_four = g_CTHashRandomValues[byte_four ^ n]; + } while(n!='\0') ; + + TargetType idx = ( byte_four << 24 ) | ( byte_three << 16 ) | ( byte_two << 8 ) | byte_one; + return ( idx ); + } +}; + +// +// This functor compares two objects of a particular type and returns a result +// that follows the strcmp/memcmp. +// If the type doesn't provide an operator== or operator< then you can provide +// a type specific specialization to override this defualt functor ! +// +template<typename T> +struct ComparisonFunctor +{ + int operator()(const T &lhs, const T &rhs) const + { + if( lhs == rhs ) + return 0 ; + else if( lhs < rhs ) + return -1 ; + else + return 1 ; + } + +}; + +// +// I expect people to build Comparison specializations for full types, +// not pointer types - this Specialization should allow the C++ compiler +// to bind to a specialization for a particular type. +// We do not want to compare pointers for equality, and a memcmp() may +// not be good for a complicated type ! +// +template<typename T> +struct ComparisonFunctor<T*> +{ + int operator()(const T *lhs, const T *rhs) const + { + ComparisonFunctor<T> functor ; + return functor(*lhs, *rhs) ; + } +}; + +template<> +struct ComparisonFunctor<int> +{ + int operator()(const int lhs, const int rhs) const + { + return lhs-rhs ; + } +}; + +template<> +struct ComparisonFunctor<unsigned int> +{ + int operator()(const unsigned int lhs, const unsigned int rhs) const + { + return static_cast<int>(lhs) - static_cast<int>(rhs) ; + } +}; + + +template<> +struct ComparisonFunctor<uint64> +{ + int operator()(const uint64 lhs, const uint64 rhs) const + { + if( lhs < rhs ) + return -1 ; + else if( lhs == rhs ) + return 0 ; + else + return 1 ; + } +}; + +template<> +struct ComparisonFunctor<char*> +{ + int operator()(const char * lhs, const char* rhs) const + { + return Q_strcmp(lhs, rhs) ; + } +}; + + +#endif // _PEARSONSHASH_H_ + + diff --git a/gcsdk/steamextra/tier1/tsmempool.cpp b/gcsdk/steamextra/tier1/tsmempool.cpp new file mode 100644 index 0000000..71b0f5b --- /dev/null +++ b/gcsdk/steamextra/tier1/tsmempool.cpp @@ -0,0 +1,255 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +//============================================================================= + + +//#include "pch_vstdlib.h" + +#include "stdafx.h" +#include "tier0/tslist.h" +#include "tier0/t0constants.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static const uint k_cubBytesAllocatedToConsiderFreeingMemory = 5 * k_nMegabyte; +static const int k_cBlocksAllocatedToConsiderFreeingMemory = 10; + +typedef TSLNodeBase_t FreeListItem_t; + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CThreadSafeMemoryPool::CThreadSafeMemoryPool( int blockSize, int numElements, int growMode ) +{ + m_ptslistFreeBlocks = new CTSListBase; + + // round up to the nearest 8-byte boundary + if ( blockSize % TSLIST_NODE_ALIGNMENT != 0 ) + { + blockSize += TSLIST_NODE_ALIGNMENT - (blockSize % TSLIST_NODE_ALIGNMENT); + } + Assert( blockSize % TSLIST_NODE_ALIGNMENT == 0 ); + Assert( blockSize > sizeof(FreeListItem_t) ); + m_nGrowMode = growMode; + m_cubBlockSize = blockSize; + m_nGrowSize = numElements; + m_cubAllocated = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Frees the memory contained in the mempool +//----------------------------------------------------------------------------- +CThreadSafeMemoryPool::~CThreadSafeMemoryPool() +{ + AUTO_LOCK_SPIN_WRITE( m_threadRWLock ); + FOR_EACH_VEC( m_vecBlockSets, i ) + { + _aligned_free( m_vecBlockSets[i].m_pubBlockSet ); + } + + delete m_ptslistFreeBlocks; +} + + +//----------------------------------------------------------------------------- +// Purpose: Frees everything +//----------------------------------------------------------------------------- +void CThreadSafeMemoryPool::Clear() +{ + AUTO_LOCK_SPIN_WRITE( m_threadRWLock ); + ClearNoLock(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Frees everything +//----------------------------------------------------------------------------- +void CThreadSafeMemoryPool::ClearNoLock() +{ + FOR_EACH_VEC( m_vecBlockSets, i ) + { + _aligned_free( m_vecBlockSets[i].m_pubBlockSet ); + } + m_ptslistFreeBlocks->Detach(); + m_cubAllocated = 0; + m_cBlocksInUse = 0; + m_vecBlockSets.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Allocates a single block of memory from the pool. +//----------------------------------------------------------------------------- +void *CThreadSafeMemoryPool::Alloc() +{ + return Alloc( m_cubBlockSize ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Allocates a single block of memory from the pool. +//----------------------------------------------------------------------------- +void *CThreadSafeMemoryPool::Alloc( unsigned int amount ) +{ + // loop attempting to get memory + // there appears to be a case where memory corruption can get this into an infinite loop + // normally 1 or 2 attempts are necessary to get a block, so if we hit 1000 we know something is wrong + int cAttempts = 1000; + while ( --cAttempts ) + { + // pull first from the free list + m_threadRWLock.LockForRead(); + FreeListItem_t *pFreeListItem = m_ptslistFreeBlocks->Pop(); + if ( pFreeListItem ) + { + m_threadRWLock.UnlockRead(); + m_cBlocksInUse++; + return (void *)pFreeListItem; + } + m_threadRWLock.UnlockRead(); + + // no free items, add a new block + + // switch from a read lock to a write lock + AUTO_LOCK_SPIN_WRITE( m_threadRWLock ); + + // another thread may have allocated memory; try the free list again if so + if ( m_ptslistFreeBlocks->Count() > 0 ) + continue; + + size_t cubBlob = m_nGrowSize * m_cubBlockSize; + if ( m_nGrowMode == GROW_FAST ) + { + cubBlob *= (m_vecBlockSets.Count() + 1); + } + + // don't grow if we're told not to + if ( m_nGrowMode == GROW_NONE && m_vecBlockSets.Count() == 1 ) + return NULL; + + // allocate, but we can fail + byte *pBlobBase = (byte *)MemAlloc_AllocAligned( cubBlob, TSLIST_NODE_ALIGNMENT /*, (m_nGrowMode == GROW_TIL_YOU_CANT)*/ ); + if ( !pBlobBase ) + return NULL; + + byte *pBlobEnd = pBlobBase + cubBlob; + // add all the items to the pool + for ( byte *pBlob = pBlobBase; pBlob < pBlobEnd; pBlob += m_cubBlockSize ) + { + m_ptslistFreeBlocks->Push( (FreeListItem_t *)pBlob ); + } + + m_cubAllocated += cubBlob; + BlockSet_t blockSet = { pBlobBase, cubBlob }; + m_vecBlockSets.AddToTail( blockSet ); + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Frees a block of memory +//----------------------------------------------------------------------------- +void CThreadSafeMemoryPool::Free( void *pMem ) +{ + Free( pMem, m_cubBlockSize ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Frees a block of memory +//----------------------------------------------------------------------------- +void CThreadSafeMemoryPool::Free( void *pMem, int cubAlloc ) +{ + m_threadRWLock.LockForRead(); + + // push the item back onto the free list + m_ptslistFreeBlocks->Push( (FreeListItem_t *)pMem ); + m_cBlocksInUse--; + + m_threadRWLock.UnlockRead(); + + // if we're completely free, and have too much memory allocated, free some + if ( m_cBlocksInUse == 0 + && m_cubAllocated >= k_cubBytesAllocatedToConsiderFreeingMemory + && m_vecBlockSets.Count() >= k_cBlocksAllocatedToConsiderFreeingMemory ) + { + AUTO_LOCK_SPIN_WRITE( m_threadRWLock ); + + // check again nothing is in use + if ( m_cBlocksInUse == 0 ) + { + // free all the blocks + ClearNoLock(); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: display +//----------------------------------------------------------------------------- +void CThreadSafeMemoryPool::PrintStats() +{ + AUTO_LOCK_SPIN_WRITE( m_threadRWLock ); + int cBlocksInUse = m_cBlocksInUse; + Msg( "Block size: %-11s Alloc'd: %8d Num blobs: %5d (%s)\n", Q_pretifymem( m_cubBlockSize, 2, true ), + cBlocksInUse, m_vecBlockSets.Count(), Q_pretifymem( m_cubAllocated, 2, true ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +size_t CThreadSafeMemoryPool::CubTotalSize() +{ + return m_cubAllocated; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +size_t CThreadSafeMemoryPool::CubSizeInUse() +{ + return m_cBlocksInUse * m_cubBlockSize; +} + + +//----------------------------------------------------------------------------- +// Purpose: data accessor +//----------------------------------------------------------------------------- +int CThreadSafeMemoryPool::Count() +{ + return m_cBlocksInUse; +} + + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Run a global validation pass on all of our data structures and memory +// allocations. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void CThreadSafeMemoryPool::Validate( CValidator &validator, const char *pchName ) +{ + AUTO_LOCK_SPIN_WRITE( m_threadRWLock ); + VALIDATE_SCOPE(); + FOR_EACH_VEC( m_vecBlockSets, i ) + { + validator.ClaimMemory( MemAlloc_Unalign( m_vecBlockSets[i].m_pubBlockSet ) ); + } + ValidateObj( m_vecBlockSets ); + validator.ClaimMemory( MemAlloc_Unalign( m_ptslistFreeBlocks ) ); +} +#endif // DBGFLAG_VALIDATE diff --git a/gcsdk/steamextra/tier1/tsmempool.h b/gcsdk/steamextra/tier1/tsmempool.h new file mode 100644 index 0000000..402681c --- /dev/null +++ b/gcsdk/steamextra/tier1/tsmempool.h @@ -0,0 +1,170 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TSMEMPOOL_H +#define TSMEMPOOL_H + +#ifdef _WIN32 +#pragma once +#endif + +#undef new + +//----------------------------------------------------------------------------- +// Purpose: Optimized pool memory allocator +//----------------------------------------------------------------------------- +class CThreadSafeMemoryPool +{ +public: + enum + { + GROW_NONE=0, // Don't allow new blobs. + GROW_FAST=1, // New blob size is numElements * (i+1) (ie: the blocks it allocates get larger and larger each time it allocates one). + GROW_SLOW=2, // New blob size is numElements. + GROW_TIL_YOU_CANT=3 // GROW_SLOW til alloc fails - then STOP and dont assert! + }; + + CThreadSafeMemoryPool( int blockSize, int numElements, int growMode = GROW_FAST ); + ~CThreadSafeMemoryPool(); + + void *Alloc(); // Allocate the element size you specified in the constructor. + void *Alloc( unsigned int cubAlloc ); + void Free( void *pMem ); + void Free( void *pMem, int cubAlloc ); + + // Frees everything + void Clear(); + + // display + void PrintStats(); + size_t CubTotalSize(); + size_t CubSizeInUse(); + int Count(); + + static void * operator new( size_t size ) + { + CThreadSafeMemoryPool *pNode = (CThreadSafeMemoryPool *)MemAlloc_AllocAligned( size, 8, __FILE__, __LINE__ +#ifdef STEAM + , true // new operator +#endif + ); + return pNode; + } + + static void * operator new( size_t size, int nBlockUse, const char *pFileName, int nLine ) + { + CThreadSafeMemoryPool *pNode = (CThreadSafeMemoryPool *)MemAlloc_AllocAligned( size, 8, pFileName, nLine +#ifdef STEAM + , true // new operator +#endif + ); + return pNode; + } + + static void operator delete( void *p) + { + MemAlloc_FreeAligned( p +#ifdef STEAM + , true // new operator +#endif + ); + } + + static void operator delete( void *p, int nBlockUse, const char *pFileName, int nLine ) + { + MemAlloc_FreeAligned( p +#ifdef STEAM + , true // new operator +#endif + ); + } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures +#endif // DBGFLAG_VALIDATE + +private: + // These ain't gonna work + static void * operator new[] ( size_t size ); + static void operator delete [] ( void *p); + + // CThreadSpinRWLock needs 8 byte alignment to work but we new() CThreadSafeMemoryPool + // so simply place it at the start of the class to make sure it fits on the 8-byte boundary + CThreadSpinRWLock m_threadRWLock; + + int m_nGrowMode; + int m_cubBlockSize; + int m_nGrowSize; + + void ClearNoLock(); + + CInterlockedInt m_cBlocksInUse; + size_t m_cubAllocated; + + struct BlockSet_t + { + byte *m_pubBlockSet; + size_t m_cubAllocated; + }; + CUtlVector<BlockSet_t> m_vecBlockSets; + + class CTSListBase *m_ptslistFreeBlocks; +}; + + +//----------------------------------------------------------------------------- +// Wrapper macro to make an allocator that returns particular typed allocations +// and construction and destruction of objects. +//----------------------------------------------------------------------------- +template< class T > +class CThreadSafeClassMemoryPool : public CThreadSafeMemoryPool +{ +public: + CThreadSafeClassMemoryPool(int numElements, int growMode = GROW_FAST) : + CThreadSafeMemoryPool( sizeof(T), numElements, growMode ) {} + + T* Alloc(); + void Free( T *pMem ); +}; + + +template< class T > +T* CThreadSafeClassMemoryPool<T>::Alloc() +{ + T *pRet = (T*)CThreadSafeMemoryPool::Alloc(); + if ( pRet ) + { + Construct( pRet ); + } + return pRet; +} + + +template< class T > +void CThreadSafeClassMemoryPool<T>::Free(T *pMem) +{ + if ( pMem ) + { + Destruct( pMem ); + } + + CThreadSafeMemoryPool::Free( pMem ); +} + + +#endif // TSMEMPOOL_H diff --git a/gcsdk/steamextra/tier1/tsmultimempool.cpp b/gcsdk/steamextra/tier1/tsmultimempool.cpp new file mode 100644 index 0000000..86a8b50 --- /dev/null +++ b/gcsdk/steamextra/tier1/tsmultimempool.cpp @@ -0,0 +1,383 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include <stdafx.h> +#include "tier0/t0constants.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +static const int k_cubMemBlockPrefixSize = sizeof(uint32); + +#define ALLOCSIZE_TO_LOOKUP( cubAlloc ) ( (cubAlloc - 1) >> 5 ) +#define LOOKUP_TO_ALLOCSIZE( iLookup ) ( (iLookup << 5) + 1 ) + + +//----------------------------------------------------------------------------- +// Purpose: constructor, the sizes in pMemPoolConfig must be in ascending order +//----------------------------------------------------------------------------- +CThreadSafeMultiMemoryPool::CThreadSafeMultiMemoryPool( const MemPoolConfig_t *pMemPoolConfig, int cnMemPoolConfig, int nGrowMode /*= GROW_FAST*/ ) +{ + m_cubReallocedTotal = 0; + m_MapRawAllocation.SetLessFunc( DefLessFunc( void * ) ); + + for ( int iMemPoolConfig = 0; iMemPoolConfig < cnMemPoolConfig; iMemPoolConfig++ ) + { + MemPoolRecord_t memPoolRecord; + // verify that the mem pool sizes are in ascending order + Assert( iMemPoolConfig == 0 || ( iMemPoolConfig > 0 && pMemPoolConfig[ iMemPoolConfig - 1 ].m_cubBlockSize < pMemPoolConfig[ iMemPoolConfig].m_cubBlockSize ) ); + AssertMsg( pMemPoolConfig[ iMemPoolConfig].m_cubBlockSize % 32 == 0, "Mempools sizes must be multiples of 32" ); + // add an int to the block size so we can note the alloc size + memPoolRecord.m_pMemPool = new CThreadSafeMemoryPool( pMemPoolConfig[ iMemPoolConfig ].m_cubBlockSize + k_cubMemBlockPrefixSize, + pMemPoolConfig[ iMemPoolConfig ].m_cubDefaultPoolSize, nGrowMode ); + Assert( memPoolRecord.m_pMemPool ); + memPoolRecord.m_nBlockSize = pMemPoolConfig[ iMemPoolConfig ].m_cubBlockSize; + m_VecMemPool.AddToTail( memPoolRecord ); + + // update the largest blocksize + m_nBlockSizeMax = MAX( m_nBlockSizeMax, memPoolRecord.m_nBlockSize ); + } + + // build the lookup table + int nLookupMax = m_nBlockSizeMax >> 5; + m_VecMemPoolLookup.AddMultipleToTail( nLookupMax ); + for ( int i = 0; i < nLookupMax; i++ ) + { + uint32 cubAllocSize = LOOKUP_TO_ALLOCSIZE( i ); + for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ ) + { + if ( m_VecMemPool[iMemPool].m_nBlockSize >= cubAllocSize ) + { + m_VecMemPoolLookup[i] = &m_VecMemPool[iMemPool]; + break; + } + } + } + +#if defined(_DEBUG) + // validate the lookup table + for ( int i = 1; i < (int)m_nBlockSizeMax; i++ ) + { + for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ ) + { + if ( (int)m_VecMemPool[iMemPool].m_nBlockSize >= i ) + { + AssertMsg( m_VecMemPoolLookup[ALLOCSIZE_TO_LOOKUP(i)] == &m_VecMemPool[iMemPool], "Invalid mempool block size, can't generate lookup table" ); + break; + } + } + } +#endif // _DEBUG +} + + +//----------------------------------------------------------------------------- +// Purpose: destructor +//----------------------------------------------------------------------------- +CThreadSafeMultiMemoryPool::~CThreadSafeMultiMemoryPool() +{ + AUTO_LOCK( m_mutexRawAllocations ); + + for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool ++ ) + { + delete m_VecMemPool[iMemPool].m_pMemPool; + } + + FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation ) + { + FreePv( m_MapRawAllocation[iRawAllocation].m_pvMem ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Allocates a block of memory at of least nAllocSize bytes +// Input : nAllocSize - number of bytes to alloc +// Output : pointer to memory alloc'd, NULL on error +//----------------------------------------------------------------------------- +void *CThreadSafeMultiMemoryPool::Alloc( uint32 cubAllocSize ) +{ + if ( cubAllocSize == 0 ) + return NULL; + + if ( cubAllocSize <= m_nBlockSizeMax ) + { + MemPoolRecord_t *pMemPoolRecord = m_VecMemPoolLookup[ALLOCSIZE_TO_LOOKUP( cubAllocSize )]; + void *pvMem = pMemPoolRecord->m_pMemPool->Alloc( cubAllocSize + k_cubMemBlockPrefixSize ); + *(uint32 *)pvMem = cubAllocSize; + return ( (char *)pvMem + k_cubMemBlockPrefixSize ); + } + + + // can't fit in our mem pools, alloc it in our one off buffer + RawAllocation_t rawAllocation; + rawAllocation.m_nBlockSize = cubAllocSize; + rawAllocation.m_pvMem = PvAlloc( cubAllocSize + k_cubMemBlockPrefixSize ); + if ( !rawAllocation.m_pvMem ) + { + return NULL; + } + *(uint32 *)rawAllocation.m_pvMem = rawAllocation.m_nBlockSize; + AUTO_LOCK( m_mutexRawAllocations ); + m_MapRawAllocation.Insert( rawAllocation.m_pvMem, rawAllocation ); + return ( (char *)rawAllocation.m_pvMem + k_cubMemBlockPrefixSize ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Free a previously alloc'd block +// Input : pMem - memory to free +//----------------------------------------------------------------------------- +void CThreadSafeMultiMemoryPool::Free( void *pvMem ) +{ + if ( !pvMem ) + return; + + uint32 cubAllocSize = *( (uint32 *)pvMem - 1 ); + + if ( cubAllocSize <= m_nBlockSizeMax ) + { + MemPoolRecord_t *pMemPoolRecord = m_VecMemPoolLookup[ALLOCSIZE_TO_LOOKUP( cubAllocSize )]; + pMemPoolRecord->m_pMemPool->Free( (char *)pvMem - k_cubMemBlockPrefixSize, cubAllocSize + k_cubMemBlockPrefixSize ); + return; + } + + AUTO_LOCK( m_mutexRawAllocations ); + + // must have been alloc'd from the raw heap, find it in map + void *pvAllocedMem = (char *)pvMem - k_cubMemBlockPrefixSize; + int iRawAllocation = m_MapRawAllocation.Find( pvAllocedMem ); + if ( m_MapRawAllocation.InvalidIndex() == iRawAllocation ) + { + AssertMsg3( false, "CThreadSafeMultiMemoryPool::Free: raw allocation %p (original alloc: %p, %d bytes) not found in allocation map", + pvMem, pvAllocedMem, cubAllocSize ); + return; + + } + + FreePv( m_MapRawAllocation[iRawAllocation].m_pvMem ); + m_MapRawAllocation.RemoveAt( iRawAllocation); +} + + +//----------------------------------------------------------------------------- +// Purpose: Return the size alloc'd for this block +// Input : pMem - memory to report +// Output : size in bytes of this memory +//----------------------------------------------------------------------------- +int CThreadSafeMultiMemoryPool::CubAllocSize(void *pvMem) +{ + if ( !pvMem ) + { + return -1; + } + + return *(((uint32 *)pvMem) -1); +} + + +//----------------------------------------------------------------------------- +// Purpose: Frees all previously alloc'd memory +//----------------------------------------------------------------------------- +void CThreadSafeMultiMemoryPool::Clear() +{ + AUTO_LOCK( m_mutexRawAllocations ); + + for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ ) + { + m_VecMemPool[iMemPool].m_pMemPool->Clear(); + } + + FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation ) + { + FreePv( m_MapRawAllocation[iRawAllocation].m_pvMem ); + } + m_MapRawAllocation.RemoveAll(); +} + + +//----------------------------------------------------------------------------- +// Purpose: print to the console info about our storage +//----------------------------------------------------------------------------- +void CThreadSafeMultiMemoryPool::PrintStats() +{ + for ( int iMemPool= 0; iMemPool < m_VecMemPool.Count(); iMemPool++ ) + { + m_VecMemPool[iMemPool].m_pMemPool->PrintStats(); + } + int cubRawBytesAllocd = 0; + AUTO_LOCK( m_mutexRawAllocations ); + FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation ) + { + cubRawBytesAllocd += m_MapRawAllocation[iRawAllocation].m_nBlockSize; + } + Msg( "Raw bytes alloc'd: %s\n", Q_pretifymem( cubRawBytesAllocd, 2, true ) ); + Msg( "Cumulative bytes re-alloced: %s\n", Q_pretifymem( m_cubReallocedTotal, 2, true ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: return the total mem alloced by this pool in MB +//----------------------------------------------------------------------------- +int CThreadSafeMultiMemoryPool::CMBPoolSize() const +{ + uint64 cubRawBytesAllocd = 0; + for ( int iMemPool= 0; iMemPool < m_VecMemPool.Count(); iMemPool++ ) + { + cubRawBytesAllocd += ( m_VecMemPool[iMemPool].m_pMemPool->CubTotalSize() ); + } + AUTO_LOCK( m_mutexRawAllocations ); + FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation ) + { + cubRawBytesAllocd += m_MapRawAllocation[iRawAllocation].m_nBlockSize; + } + + return ( cubRawBytesAllocd / k_nMegabyte ); +} + +//----------------------------------------------------------------------------- +// Purpose: return the total mem alloced by this pool in MB +//----------------------------------------------------------------------------- +int CThreadSafeMultiMemoryPool::CMBPoolSizeInUse() const +{ + uint64 cubRawBytesAllocd = 0; + for ( int iMemPool= 0; iMemPool < m_VecMemPool.Count(); iMemPool++ ) + { + cubRawBytesAllocd += ( m_VecMemPool[iMemPool].m_pMemPool->CubSizeInUse() ); + } + AUTO_LOCK( m_mutexRawAllocations ); + FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation ) + { + cubRawBytesAllocd += m_MapRawAllocation[iRawAllocation].m_nBlockSize; + } + + return ( cubRawBytesAllocd / k_nMegabyte ); +} + + +//----------------------------------------------------------------------------- +// Purpose: return number of mempool blocks alloc'd +//----------------------------------------------------------------------------- +int CThreadSafeMultiMemoryPool::Count() +{ + int cCount = 0; + for ( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ ) + { + cCount += m_VecMemPool[iMemPool].m_pMemPool->Count(); + } + return cCount; +} + + +//----------------------------------------------------------------------------- +// Purpose: reallocate an existing block of memory to a new size (and copy the data +// Input: pvMem - a pointer to the existing memory +// cubAlloc - number of bytes to alloc +// Output: returns a pointer to the memory allocated (NULL on error) +//----------------------------------------------------------------------------- +void *CThreadSafeMultiMemoryPool::ReAlloc( void *pvMem, uint32 cubAlloc ) +{ + uint32 cubOldAlloc = CubAllocSize(pvMem); + if ( pvMem && cubAlloc <= cubOldAlloc ) + return pvMem; + + if ( cubOldAlloc > m_nBlockSizeMax ) + { + AUTO_LOCK( m_mutexRawAllocations ); + // okay, must have been alloc'd from the raw heap, search for it + void *pvAllocedMem = (char *)pvMem - k_cubMemBlockPrefixSize; + int iRawAllocation = m_MapRawAllocation.Find( pvAllocedMem ); + if ( m_MapRawAllocation.InvalidIndex() == iRawAllocation ) + { + AssertMsg3( false, "CThreadSafeMultiMemoryPool::ReAlloc: raw allocation %p (original alloc: %p, %d bytes) not found in allocation map", + pvMem, pvAllocedMem, cubOldAlloc ); + return NULL; + } + + // realloc the memory + void *pvNewMem = PvRealloc( pvAllocedMem, cubAlloc + k_cubMemBlockPrefixSize ); + if ( !pvNewMem ) + { + m_MapRawAllocation.RemoveAt( iRawAllocation ); + return NULL; + } + + // update our tracking + *(uint32 *)pvNewMem = cubAlloc; + if ( pvAllocedMem == pvNewMem ) + { + // if pointer is the same, use the same map entry with the same key (the pointer given to caller) + m_MapRawAllocation[iRawAllocation].m_pvMem = pvNewMem; + m_MapRawAllocation[iRawAllocation].m_nBlockSize = cubAlloc; + } + else + { + // if pointer changed, need to remove the old entry and re-insert with new key + m_MapRawAllocation.RemoveAt( iRawAllocation ); + RawAllocation_t rawAllocation; + rawAllocation.m_pvMem = pvNewMem; + rawAllocation.m_nBlockSize = cubAlloc; + m_MapRawAllocation.Insert( rawAllocation.m_pvMem, rawAllocation ); + } + return ( (char *)pvNewMem + k_cubMemBlockPrefixSize ); + } + else + { + // see if we can stay in the same block + MemPoolRecord_t *pMemPoolRecord = m_VecMemPoolLookup[ALLOCSIZE_TO_LOOKUP( cubOldAlloc )]; + if ( cubAlloc <= pMemPoolRecord->m_nBlockSize ) + { + // re-assign the size + *((uint32 *)pvMem - 1) = cubAlloc; + return pvMem; + } + + void *pvNewMem = Alloc( cubAlloc ); + if ( !pvNewMem ) + { + return NULL; + } + m_cubReallocedTotal += cubOldAlloc; + Q_memcpy( pvNewMem, pvMem, cubOldAlloc ); + Free( pvMem ); // now free the old memory buffer we had + return pvNewMem; + } +} + + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void CThreadSafeMultiMemoryPool::Validate( CValidator &validator, const char *pchName ) +{ + validator.Push( "CThreadSafeMultiMemoryPool", this, pchName ); + + ValidateObj( m_VecMemPool ); + for( int iMemPool = 0; iMemPool < m_VecMemPool.Count(); iMemPool++ ) + { + validator.ClaimMemory_Aligned( m_VecMemPool[iMemPool].m_pMemPool ); + m_VecMemPool[iMemPool].m_pMemPool->Validate( validator, "m_VecMemPool[iMemPool].m_pMemPool" ); + } + + AUTO_LOCK( m_mutexRawAllocations ); + ValidateObj( m_MapRawAllocation ); + FOR_EACH_MAP_FAST( m_MapRawAllocation, iRawAllocation ) + { + validator.ClaimMemory( m_MapRawAllocation[iRawAllocation].m_pvMem ); + } + + ValidateObj( m_VecMemPoolLookup ); + + validator.Pop(); +} +#endif // DBGFLAG_VALIDATE + diff --git a/gcsdk/steamextra/tier1/tsmultimempool.h b/gcsdk/steamextra/tier1/tsmultimempool.h new file mode 100644 index 0000000..860e9df --- /dev/null +++ b/gcsdk/steamextra/tier1/tsmultimempool.h @@ -0,0 +1,97 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TSMULTIMEMPOOL_H +#define TSMULTIMEMPOOL_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlmap.h" +#include "tier1/mempool.h" +#include "tier1/tsmempool.h" + +//----------------------------------------------------------------------------- +// Purpose: A container of a range of mem pool sizes (for network buffers for example) +// and a raw alloc capability (for sizes greater than any contained mem pool). +//----------------------------------------------------------------------------- +class CThreadSafeMultiMemoryPool +{ +public: + struct MemPoolConfig_t + { + uint32 m_cubBlockSize; + uint32 m_cubDefaultPoolSize; + }; + + CThreadSafeMultiMemoryPool( const MemPoolConfig_t *pnBlock, int cnMemPoolConfig, int nGrowMode = UTLMEMORYPOOL_GROW_FAST ); + ~CThreadSafeMultiMemoryPool(); + + // Allocate a block of at least nAllocSize bytes + void* Alloc( uint32 cubAlloc ); + // Free a previously alloc'd block + void Free(void *pvMem); + // ReAllocate a previously allocated block to a new size + void* ReAlloc( void *pvMem, uint32 cubAlloc ); + + // Frees everything + void Clear(); + + // alloc size for this bit of alloc'd memory + int CubAllocSize( void *pvMem ); + // prints details about our contained memory + void PrintStats(); + + // total number of alloc'd elements + int Count(); + + // Return the total size in MB allocated for this pool + int CMBPoolSize() const; + // Return the amount of memory in use + int CMBPoolSizeInUse() const; + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ); // Validate our internal structures +#endif // DBGFLAG_VALIDATE + +private: + struct MemPoolRecord_t + { + CThreadSafeMemoryPool *m_pMemPool; + uint32 m_nBlockSize; + }; + + CUtlVector<MemPoolRecord_t> m_VecMemPool; // stores our list of mem pools + + uint32 m_nBlockSizeMax; + CUtlVector<MemPoolRecord_t *> m_VecMemPoolLookup; // quick lookup table of mempools + + struct RawAllocation_t + { + void *m_pvMem; + uint32 m_nBlockSize; + }; + CUtlMap<void *,RawAllocation_t,int> m_MapRawAllocation; // stores our list of raw alloc'd mem + CThreadFastMutex m_mutexRawAllocations; + + uint32 m_cubReallocedTotal; +}; + + +#endif // TSMULTIMEMPOOL_H diff --git a/gcsdk/steamextra/tier1/utlhashmaplarge.h b/gcsdk/steamextra/tier1/utlhashmaplarge.h new file mode 100644 index 0000000..5525930 --- /dev/null +++ b/gcsdk/steamextra/tier1/utlhashmaplarge.h @@ -0,0 +1,693 @@ +//========= Copyright Valve Corporation, All rights reserved. =================// +// +// Purpose: index-based hash map container well suited for large and growing +// datasets. It uses less memory than other hash maps and incrementally +// rehashes to reduce reallocation spikes. +// +//=============================================================================// + +#ifndef UTLHASHMAPLARGE_H +#define UTLHASHMAPLARGE_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tier0/dbg.h" +#include "bitvec.h" +#include "tier1/murmurhash3.h" + +// fast mod for power of 2 numbers +namespace basetypes +{ +template <class T> +inline bool IsPowerOf2(T n) +{ + return n > 0 && (n & (n-1)) == 0; +} + +template <class T1, class T2> +inline T2 ModPowerOf2(T1 a, T2 b) +{ + return T2(a) & (b-1); +} +} + +// default comparison operator +template <typename T> +class CDefEquals +{ +public: + CDefEquals() {} + CDefEquals( int i ) {} + inline bool operator()( const T &lhs, const T &rhs ) const { return ( lhs == rhs ); } + inline bool operator!() const { return false; } +}; + + +// Specialization to compare pointers +template <typename T> +class CDefEquals<T*> +{ +public: + CDefEquals() {} + CDefEquals( int i ) {} + inline bool operator()( const T *lhs, const T *rhs ) const + { + if ( lhs == rhs ) + return true; + else if ( NULL == lhs || NULL == rhs ) + return false; + else + return ( *lhs == *rhs ); + } + inline bool operator!() const { return false; } +}; + + +// Hash specialization for CUtlStrings +template<> +struct MurmurHash3Functor<CUtlString> +{ + typedef uint32 TargetType ; + TargetType operator()(const CUtlString &strKey) const + { + return MurmurHash3Functor<const char*>()( strKey.String() ); + } +}; + +//hash 3 function for a general case sensitive string compares +struct MurmurHash3ConstCharPtr +{ + typedef uint32 TargetType ; + TargetType operator()( const char* pszKey ) const { return MurmurHash3Functor<const char*>()( pszKey ); } +}; +struct CaseSensitiveStrEquals +{ + bool operator()( const char* pszLhs, const char* pszRhs ) const { return strcmp( pszLhs, pszRhs ) == 0; } +}; + +//----------------------------------------------------------------------------- +// +// Purpose: An associative container. Pretty much identical to CUtlMap without the ability to walk in-order +// This container is well suited for large and growing datasets. It uses less +// memory than other hash maps and incrementally rehashes to reduce reallocation spikes. +// However, it is slower (by about 20%) than CUtlHashTable +// +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L = CDefEquals<K>, typename H = MurmurHash3Functor<K> > +class CUtlHashMapLarge +{ +public: + // This enum exists so that FOR_EACH_MAP and FOR_EACH_MAP_FAST cannot accidentally + // be used on a type that is not a CUtlMap. If the code compiles then all is well. + // The check for IsUtlMap being true should be free. + // Using an enum rather than a static const bool ensures that this trick works even + // with optimizations disabled on gcc. + enum CompileTimeCheck + { + IsUtlMap = 1 + }; + + typedef K KeyType_t; + typedef T ElemType_t; + typedef int IndexType_t; + typedef L EqualityFunc_t; + typedef H HashFunc_t; + + CUtlHashMapLarge() + { + m_cElements = 0; + m_nMaxElement = 0; + m_nMinRehashedBucket = InvalidIndex(); + m_nMaxRehashedBucket = InvalidIndex(); + m_iNodeFreeListHead = InvalidIndex(); + } + + CUtlHashMapLarge( int cElementsExpected ) + { + m_cElements = 0; + m_nMaxElement = 0; + m_nMinRehashedBucket = InvalidIndex(); + m_nMaxRehashedBucket = InvalidIndex(); + m_iNodeFreeListHead = InvalidIndex(); + EnsureCapacity( cElementsExpected ); + } + + ~CUtlHashMapLarge() + { + RemoveAll(); + } + + // gets particular elements + ElemType_t & Element( IndexType_t i ) { return m_memNodes.Element( i ).m_elem; } + const ElemType_t & Element( IndexType_t i ) const { return m_memNodes.Element( i ).m_elem; } + ElemType_t & operator[]( IndexType_t i ) { return m_memNodes.Element( i ).m_elem; } + const ElemType_t & operator[]( IndexType_t i ) const { return m_memNodes.Element( i ).m_elem; } + KeyType_t & Key( IndexType_t i ) { return m_memNodes.Element( i ).m_key; } + const KeyType_t & Key( IndexType_t i ) const { return m_memNodes.Element( i ).m_key; } + + // Num elements + IndexType_t Count() const { return m_cElements; } + + // Max "size" of the vector + IndexType_t MaxElement() const { return m_nMaxElement; } + + // Checks if a node is valid and in the map + bool IsValidIndex( IndexType_t i ) const { return i >= 0 && i < m_nMaxElement && !IsFreeNodeID( m_memNodes[i].m_iNextNode ); } + + // Invalid index + static IndexType_t InvalidIndex() { return -1; } + + // Insert method + IndexType_t Insert( const KeyType_t &key, const ElemType_t &insert ) { return InsertInternal( key, insert, eInsert_UpdateExisting ); } + IndexType_t Insert( const KeyType_t &key ) { return InsertInternal( key, ElemType_t(), eInsert_UpdateExisting ); } + IndexType_t InsertWithDupes( const KeyType_t &key, const ElemType_t &insert ) { return InsertInternal( key, insert, eInsert_CreateDupes ); } + IndexType_t FindOrInsert( const KeyType_t &key, const ElemType_t &insert ) { return InsertInternal( key, insert, eInsert_LeaveExisting ); } + IndexType_t InsertOrReplace( const KeyType_t &key, const ElemType_t &insert ) { return InsertInternal( key, insert, eInsert_UpdateExisting ); } + + + // Finds an element + IndexType_t Find( const KeyType_t &key ) const; + + // has an element + bool HasElement( const KeyType_t &key ) const + { + return Find( key ) != InvalidIndex(); + } + + void EnsureCapacity( int num ); + + void RemoveAt( IndexType_t i ); + bool Remove( const KeyType_t &key ) + { + int iMap = Find( key ); + if ( iMap != InvalidIndex() ) + { + RemoveAt( iMap ); + return true; + } + return false; + } + void RemoveAll(); + void Purge(); + void PurgeAndDeleteElements(); + + void Swap( CUtlHashMapLarge<K,T,L,H> &rhs ) + { + m_vecHashBuckets.Swap( rhs.m_vecHashBuckets ); + V_swap( m_bitsMigratedBuckets, rhs.m_bitsMigratedBuckets ); + m_memNodes.Swap( rhs.m_memNodes ); + V_swap( m_iNodeFreeListHead, rhs.m_iNodeFreeListHead ); + V_swap( m_cElements, rhs.m_cElements ); + V_swap( m_nMaxElement, rhs.m_nMaxElement ); + V_swap( m_nMinRehashedBucket, rhs.m_nMinRehashedBucket ); + V_swap( m_nMaxRehashedBucket, rhs.m_nMaxRehashedBucket ); + V_swap( m_EqualityFunc, rhs.m_EqualityFunc ); + V_swap( m_HashFunc, rhs.m_HashFunc ); + } + +private: + enum EInsertPolicy { eInsert_UpdateExisting, eInsert_LeaveExisting, eInsert_CreateDupes }; + IndexType_t InsertInternal( const KeyType_t &key, const ElemType_t &insert, EInsertPolicy ePolicy ); + + inline IndexType_t FreeNodeIDToIndex( IndexType_t i ) const { return (0-i)-3; } + inline IndexType_t FreeNodeIndexToID( IndexType_t i ) const { return (-3)-i; } + inline bool IsFreeNodeID( IndexType_t i ) const { return i < InvalidIndex(); } + + int FindInBucket( int iBucket, const KeyType_t &key ) const; + int AllocNode(); + void RehashNodesInBucket( int iBucket ); + void LinkNodeIntoBucket( int iBucket, int iNewNode ); + void UnlinkNodeFromBucket( int iBucket, int iNewNode ); + bool RemoveNodeFromBucket( int iBucket, int iNodeToRemove ); + void IncrementalRehash(); + + struct HashBucket_t + { + IndexType_t m_iNode; + }; + CUtlVector<HashBucket_t> m_vecHashBuckets; + + CLargeVarBitVec m_bitsMigratedBuckets; + + struct Node_t + { + KeyType_t m_key; + ElemType_t m_elem; + int m_iNextNode; + }; + CUtlMemory<Node_t> m_memNodes; + IndexType_t m_iNodeFreeListHead; + + IndexType_t m_cElements; + IndexType_t m_nMaxElement; + IndexType_t m_nMinRehashedBucket, m_nMaxRehashedBucket; + EqualityFunc_t m_EqualityFunc; + HashFunc_t m_HashFunc; +}; + + +//----------------------------------------------------------------------------- +// Purpose: inserts an item into the map +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline int CUtlHashMapLarge<K,T,L,H>::InsertInternal( const KeyType_t &key, const ElemType_t &insert, EInsertPolicy ePolicy ) +{ + // make sure we have room in the hash table + if ( m_cElements >= m_vecHashBuckets.Count() ) + EnsureCapacity( MAX( 16, m_vecHashBuckets.Count() * 2 ) ); + if ( m_cElements >= m_memNodes.Count() ) + m_memNodes.Grow( m_memNodes.Count() * 2 ); + + // rehash incrementally + IncrementalRehash(); + + // hash the item + uint32 hash = m_HashFunc( key ); + + // migrate data forward, if necessary + int cBucketsToModAgainst = m_vecHashBuckets.Count() >> 1; + int iBucket = basetypes::ModPowerOf2(hash, cBucketsToModAgainst); + while ( iBucket >= m_nMinRehashedBucket + && !m_bitsMigratedBuckets.Get( iBucket ) ) + { + RehashNodesInBucket( iBucket ); + cBucketsToModAgainst >>= 1; + iBucket = basetypes::ModPowerOf2(hash, cBucketsToModAgainst); + } + + // prevent duplicates if necessary + if ( ( ePolicy != eInsert_CreateDupes ) && m_cElements ) + { + // look in the bucket to see if we have a conflict + int iBucket2 = basetypes::ModPowerOf2( hash, m_vecHashBuckets.Count() ); + IndexType_t iNode = FindInBucket( iBucket2, key ); + if ( iNode != InvalidIndex() ) + { + // a duplicate - update in place (matching CUtlMap) + if( ePolicy == eInsert_UpdateExisting ) + { + m_memNodes[iNode].m_elem = insert; + } + return iNode; + } + } + + // make an item + int iNewNode = AllocNode(); + m_memNodes[iNewNode].m_iNextNode = InvalidIndex(); + CopyConstruct( &m_memNodes[iNewNode].m_key, key ); + CopyConstruct( &m_memNodes[iNewNode].m_elem, insert ); + + iBucket = basetypes::ModPowerOf2( hash, m_vecHashBuckets.Count() ); + + // link ourselves in + // ::OutputDebugStr( CFmtStr( "insert %d into bucket %d\n", key, iBucket ).Access() ); + LinkNodeIntoBucket( iBucket, iNewNode ); + + // return the new node + return iNewNode; +} + +//----------------------------------------------------------------------------- +// Purpose: grows the map to fit the specified amount +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline void CUtlHashMapLarge<K,T,L,H>::EnsureCapacity( int amount ) +{ + m_memNodes.EnsureCapacity( amount ); + // ::OutputDebugStr( CFmtStr( "grown m_memNodes from %d to %d\n", m_cElements, m_memNodes.Count() ).Access() ); + + if ( amount <= m_vecHashBuckets.Count() ) + return; + int cBucketsNeeded = MAX( 16, m_vecHashBuckets.Count() ); + while ( cBucketsNeeded < amount ) + cBucketsNeeded *= 2; + + // ::OutputDebugStr( CFmtStr( "grown m_vecHashBuckets from %d to %d\n", m_vecHashBuckets.Count(), cBucketsNeeded ).Access() ); + + // grow the hash buckets + int grow = cBucketsNeeded - m_vecHashBuckets.Count(); + int iFirst = m_vecHashBuckets.AddMultipleToTail( grow ); + // clear all the new data to invalid bits + memset( &m_vecHashBuckets[iFirst], 0xFFFFFFFF, grow*sizeof(m_vecHashBuckets[iFirst]) ); + Assert( basetypes::IsPowerOf2( m_vecHashBuckets.Count() ) ); + + // we'll have to rehash, all the buckets that existed before growth + m_nMinRehashedBucket = 0; + m_nMaxRehashedBucket = iFirst; + if ( m_cElements > 0 ) + { + // remove all the current bits + m_bitsMigratedBuckets.Resize( 0 ); + // re-add new bits; these will all be reset to 0 + m_bitsMigratedBuckets.Resize( m_vecHashBuckets.Count() ); + } + else + { + // no elements - no rehashing + m_nMinRehashedBucket = m_vecHashBuckets.Count(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: gets a new node, from the free list if possible +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline int CUtlHashMapLarge<K,T,L,H>::AllocNode() +{ + // if we're out of free elements, get the max + if ( m_cElements == m_nMaxElement ) + { + m_cElements++; + return m_nMaxElement++; + } + + // pull from the free list + Assert( m_iNodeFreeListHead != InvalidIndex() ); + int iNewNode = m_iNodeFreeListHead; + m_iNodeFreeListHead = FreeNodeIDToIndex( m_memNodes[iNewNode].m_iNextNode ); + m_cElements++; + return iNewNode; +} + + +//----------------------------------------------------------------------------- +// Purpose: takes a bucket of nodes and re-hashes them into a more optimal bucket +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline void CUtlHashMapLarge<K,T,L,H>::RehashNodesInBucket( int iBucketSrc ) +{ + // mark us as migrated + m_bitsMigratedBuckets.Set( iBucketSrc ); + + // walk the list of items, re-hashing them + IndexType_t iNode = m_vecHashBuckets[iBucketSrc].m_iNode; + while ( iNode != InvalidIndex() ) + { + IndexType_t iNodeNext = m_memNodes[iNode].m_iNextNode; + Assert( iNodeNext != iNode ); + + // work out where the node should go + const KeyType_t &key = m_memNodes[iNode].m_key; + uint32 hash = m_HashFunc( key ); + int iBucketDest = basetypes::ModPowerOf2( hash, m_vecHashBuckets.Count() ); + + // if the hash bucket has changed, move it + if ( iBucketDest != iBucketSrc ) + { + // ::OutputDebugStr( CFmtStr( "moved key %d from bucket %d to %d\n", key, iBucketSrc, iBucketDest ).Access() ); + + // remove from this bucket list + UnlinkNodeFromBucket( iBucketSrc, iNode ); + + // link into new bucket list + LinkNodeIntoBucket( iBucketDest, iNode ); + } + iNode = iNodeNext; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: searches for an item by key, returning the index handle +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline int CUtlHashMapLarge<K,T,L,H>::Find( const KeyType_t &key ) const +{ + if ( m_cElements == 0 ) + return InvalidIndex(); + + // hash the item + uint32 hash = m_HashFunc( key ); + + // find the bucket + int cBucketsToModAgainst = m_vecHashBuckets.Count(); + int iBucket = basetypes::ModPowerOf2( hash, cBucketsToModAgainst ); + + // look in the bucket for the item + int iNode = FindInBucket( iBucket, key ); + if ( iNode != InvalidIndex() ) + return iNode; + + // not found? we may have to look in older buckets + cBucketsToModAgainst >>= 1; + while ( cBucketsToModAgainst >= m_nMinRehashedBucket ) + { + iBucket = basetypes::ModPowerOf2( hash, cBucketsToModAgainst ); + + if ( !m_bitsMigratedBuckets.Get( iBucket ) ) + { + int iNode2 = FindInBucket( iBucket, key ); + if ( iNode2 != InvalidIndex() ) + return iNode2; + } + + cBucketsToModAgainst >>= 1; + } + + return InvalidIndex(); +} + + +//----------------------------------------------------------------------------- +// Purpose: searches for an item by key, returning the index handle +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline int CUtlHashMapLarge<K,T,L,H>::FindInBucket( int iBucket, const KeyType_t &key ) const +{ + if ( m_vecHashBuckets[iBucket].m_iNode != InvalidIndex() ) + { + IndexType_t iNode = m_vecHashBuckets[iBucket].m_iNode; + Assert( iNode < m_nMaxElement ); + while ( iNode != InvalidIndex() ) + { + // equality check + if ( m_EqualityFunc( key, m_memNodes[iNode].m_key ) ) + return iNode; + + iNode = m_memNodes[iNode].m_iNextNode; + } + } + + return InvalidIndex(); +} + + +//----------------------------------------------------------------------------- +// Purpose: links a node into a bucket +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +void CUtlHashMapLarge<K,T,L,H>::LinkNodeIntoBucket( int iBucket, int iNewNode ) +{ + // add into the start of the bucket's list + m_memNodes[iNewNode].m_iNextNode = m_vecHashBuckets[iBucket].m_iNode; + m_vecHashBuckets[iBucket].m_iNode = iNewNode; +} + + +//----------------------------------------------------------------------------- +// Purpose: unlinks a node from the bucket +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +void CUtlHashMapLarge<K,T,L,H>::UnlinkNodeFromBucket( int iBucket, int iNodeToUnlink ) +{ + int iNodeNext = m_memNodes[iNodeToUnlink].m_iNextNode; + + // if it's the first node, just update the bucket to point to the new place + int iNode = m_vecHashBuckets[iBucket].m_iNode; + if ( iNode == iNodeToUnlink ) + { + m_vecHashBuckets[iBucket].m_iNode = iNodeNext; + return; + } + + // walk the list to find where + while ( iNode != InvalidIndex() ) + { + if ( m_memNodes[iNode].m_iNextNode == iNodeToUnlink ) + { + m_memNodes[iNode].m_iNextNode = iNodeNext; + return; + } + iNode = m_memNodes[iNode].m_iNextNode; + } + + // should always be valid to unlink + Assert( false ); +} + + +//----------------------------------------------------------------------------- +// Purpose: removes a single item from the map +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline void CUtlHashMapLarge<K,T,L,H>::RemoveAt( IndexType_t i ) +{ + if ( !IsValidIndex( i ) ) + { + Assert( false ); + return; + } + + // unfortunately, we have to re-hash to find which bucket we're in + uint32 hash = m_HashFunc( m_memNodes[i].m_key ); + int cBucketsToModAgainst = m_vecHashBuckets.Count(); + int iBucket = basetypes::ModPowerOf2( hash, cBucketsToModAgainst ); + if ( RemoveNodeFromBucket( iBucket, i ) ) + return; + + // wasn't found; look in older buckets + cBucketsToModAgainst >>= 1; + while ( cBucketsToModAgainst >= m_nMinRehashedBucket ) + { + iBucket = basetypes::ModPowerOf2( hash, cBucketsToModAgainst ); + + if ( !m_bitsMigratedBuckets.Get( iBucket ) ) + { + if ( RemoveNodeFromBucket( iBucket, i ) ) + return; + } + + cBucketsToModAgainst >>= 1; + } + + // never found, container is busted + Assert( false ); +} + + +//----------------------------------------------------------------------------- +// Purpose: removes a node from the bucket, return true if it was found +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline bool CUtlHashMapLarge<K,T,L,H>::RemoveNodeFromBucket( IndexType_t iBucket, int iNodeToRemove ) +{ + IndexType_t iNode = m_vecHashBuckets[iBucket].m_iNode; + while ( iNode != InvalidIndex() ) + { + if ( iNodeToRemove == iNode ) + { + // found it, remove + UnlinkNodeFromBucket( iBucket, iNodeToRemove ); + Destruct( &m_memNodes[iNode].m_key ); + Destruct( &m_memNodes[iNode].m_elem ); + + // link into free list + m_memNodes[iNode].m_iNextNode = FreeNodeIndexToID( m_iNodeFreeListHead ); + m_iNodeFreeListHead = iNode; + m_cElements--; + if ( m_cElements == 0 ) + { + m_nMinRehashedBucket = m_vecHashBuckets.Count(); + } + return true; + } + + iNode = m_memNodes[iNode].m_iNextNode; + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: removes all items from the hash map +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline void CUtlHashMapLarge<K,T,L,H>::RemoveAll() +{ + FOR_EACH_MAP_FAST( *this, i ) + { + Destruct( &m_memNodes[i].m_key ); + Destruct( &m_memNodes[i].m_elem ); + } + + m_cElements = 0; + m_nMaxElement = 0; + m_iNodeFreeListHead = InvalidIndex(); + m_nMinRehashedBucket = m_vecHashBuckets.Count(); + m_nMaxRehashedBucket = InvalidIndex(); + m_bitsMigratedBuckets.Resize( 0 ); + memset( m_vecHashBuckets.Base(), 0xFF, m_vecHashBuckets.Count() * sizeof(HashBucket_t) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: removes all items from the hash map and releases memory +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline void CUtlHashMapLarge<K,T,L,H>::Purge() +{ + FOR_EACH_MAP_FAST( *this, i ) + { + Destruct( &m_memNodes[i].m_key ); + Destruct( &m_memNodes[i].m_elem ); + } + + m_cElements = 0; + m_nMaxElement = 0; + m_iNodeFreeListHead = InvalidIndex(); + m_nMinRehashedBucket = InvalidIndex(); + m_nMaxRehashedBucket = InvalidIndex(); + + m_bitsMigratedBuckets.Resize( 0 ); + m_memNodes.Purge(); + m_vecHashBuckets.Purge(); +} + + +//----------------------------------------------------------------------------- +// Purpose: removes and deletes all items from the hash map and releases memory +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline void CUtlHashMapLarge<K,T,L,H>::PurgeAndDeleteElements() +{ + FOR_EACH_MAP_FAST( *this, i ) + { + delete this->Element( i ); + } + + Purge(); +} + + +//----------------------------------------------------------------------------- +// Purpose: rehashes buckets +//----------------------------------------------------------------------------- +template <typename K, typename T, typename L, typename H> +inline void CUtlHashMapLarge<K,T,L,H>::IncrementalRehash() +{ + if ( m_nMinRehashedBucket < m_nMaxRehashedBucket ) + { + while ( m_nMinRehashedBucket < m_nMaxRehashedBucket ) + { + // see if the bucket needs rehashing + if ( m_vecHashBuckets[m_nMinRehashedBucket].m_iNode != InvalidIndex() + && !m_bitsMigratedBuckets.Get(m_nMinRehashedBucket) ) + { + // rehash this bucket + RehashNodesInBucket( m_nMinRehashedBucket ); + // only actively do one - don't want to do it too fast since we may be on a rapid growth path + ++m_nMinRehashedBucket; + break; + } + + // nothing to rehash in that bucket - increment and look again + ++m_nMinRehashedBucket; + } + + if ( m_nMinRehashedBucket >= m_nMaxRehashedBucket ) + { + // we're done; don't need any bits anymore + m_nMinRehashedBucket = m_vecHashBuckets.Count(); + m_nMaxRehashedBucket = InvalidIndex(); + m_bitsMigratedBuckets.Resize( 0 ); + } + } +} + + +#endif // UTLHASHMAPLARGE_H |