You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden Change 3110660 on 2016/9/1 by Andrew.Grant Moved performance/quality warnings out of DrawStatsHUD into new function and now display them in everything other than shipping builds (unless disabled, or screenshot/movie dumping is in progress. HLOD warning is updated every 20 secs to deal with streaing levels. Moved debug warnings into a separate Draw function (still disabled in test, but would like to make this an option in Orion soon). #rb Michael.Noland #tests verified we see our unbuilt HLOD warning in v31 :( Change 3106649 on 2016/08/30 by Cody.Haskell #Orion - Input Axis Work #rb DanH #tests PIE Change 3106299 on 2016/08/30 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 31.2 @ CL 3105865 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3105969 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change3106213on 2016/08/30 by Ben.Marsh BuildGraph: Include UAT, UBT, and UHT binaries in precompiled binaries zip file. #rb none #tests preflight Change 3105994 on 2016/08/30 by Martin.Wilson Stop recompression happening when additive frame index is changed "interactively" (recompression will occur at end of interactive input) #jira UE-35289 #rb Thomas.Sarkanen #tests Tested UI in editor Change 3105331 on 2016/08/29 by Uriel.Doyon Allowed texture to ignore streaming MipBias with UTexture2D::bIgnoreStreamingMipBias Used this new flag when assigning texture to UImage::SetBrushFromTexture to prevent having low quality UI in low texture budget. #rb marcus.wassmer #tests launched editor and played game #jira OR-25814 Change 3105143 on 2016/08/29 by Josh.Markiewicz #UE4 - added assert when histogram input parameters don't match #rb none #tests launched/ran/won game golden path #codereview dmitry.rekman, michael.noland, bart.bressler Change 3104976 on 2016/08/29 by Jon.Lietz pickup refector - fixed a big that would allow mixed replication to call a gameplay cue's added twice. - All pickups now use the pick up manager, consolidated all pick up code into the manager. - added to the XP set so we can define the CXP bounty for targets. #RB Dave.Ratti #tests Bot match, test maps, spawning coins and pickups. Change 3103480 on 2016/08/26 by Josh.Markiewicz #UE4 - added GetSessionIdStr to FOnlineSessionSearchResult and FOnlineSession #rb none #tests golden path matchmaking #codereview paul.moore, eric.newman Change 3103410 on 2016/08/26 by Max.Chen Movie Capture: Fix commandline burnin option. #rb none #tests Render movie with commandline -UseBurnIn=yes option. Change 3102134 on 2016/08/25 by Brian.Karis Fix for HDR output exposure. Added 1000nit output option. #rb marcus #tests agora Change 3101276 on 2016/08/25 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_31 - Merging CL 3100347 (head revision of 2 files :o ) #RB:none #Tests:none [CodeReviewed]: matt.schembari, max.preussner #R@BOMERGE-SOURCE: CL 3101273 in //Orion/Release-31/... via CL 3101274 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3101267 on 2016/08/25 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_DUI - Integrating Media changes from 4.13 (head revision) #RB:none #Tests:none /Engine/Plugins/Media /Engine/Source/Runtime/Media /Engine/Source/Runtime/MediaAssets [CodeReviewed] matt.schembari, max.preussner #R@BOMERGE-SOURCE: CL 3099267 in //Orion/Dev-UI/... via CL 3101266 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3100378 on 2016/08/24 by John.Pollard Fix a crash that can occur when scrubbing in replays #codereview David.Ratti #tests Replays #rb DavidR This is the output: [2016.08.24-21.35.05:973][603]LogAbilitySystem:Warning: OnRep_ReplicatedAnimMontage: PlayMontageSimulated failed. Name: AbilitySystemComponent0, AnimMontage: LevelStart_Montage Change 3100375 on 2016/08/24 by Laurent.Delayen Added AimOffsetLookAt node. AimOffset node that drives its inputs automatically from a Target Location (and a Source Socket). #rb none #codereview lina.halper #tests Tacticia's RMB Targeting Change 3100278 on 2016/08/24 by Laurent.Delayen Fix for fast path struct copy being broken for FVectors. #rb lina.halper #codereview thomas.sarkanen #tests Chains' hook, Tacticia's LaserBeam and OrientationWarping Change 3100161 on 2016/08/24 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) Fix issue with refresh viewer command failing due to backend congestion #rb RyanG #tests Replays Change 3100114 on 2016/08/24 by jason.bestimt #ORION_MAIN - Merge DUI @ CL 3098849 #RB:none #Tests:none #CodeReview: kerrington.smith, matt.schembari #R@BOMERGE-SOURCE: CL 3100078 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3100015 on 2016/08/24 by Michael.Noland Don't allow the consideration of nodes that won't be processed to affect the live aspect of the active sound containing a cross fade node #jira UE-34998 #rb Aaron.McLeran [re-implementing CL# 3098559 originaly by Marc.Audy in Release 4.13] #tests Compiled and ran a golden path match with headphones on Change 3100012 on 2016/08/24 by Michael.Noland UE-34951 - Zero-volume vorbis decoded sounds are too expensive -Adding an audio settings parameter to disable zero-volume playback globally -Adding a new bool on sound waves to allow opt-in to virtualize when at zero-volume #rb marc.audy [re-implementing CL# 3094893 from Dev-Framework, originally by Aaron McLeran] #tests Compiled and ran a golden path match with headphones on Change 3099889 on 2016/08/24 by Max.Chen Sequencer: Added command line option to enable burnin #rb none #tests Render movie from command line wtih -UseBurnIn=yes Change 3099801 on 2016/08/24 by Lina.Halper Removed unnecessary comment #rb: none #code review: Benn.Gallagher #tests: compile Change 3099787 on 2016/08/24 by Jason.Bestimt #R@BOMERGE-AUTHOR: sam.zamani #http - fix for cancelled requests that have not been started never triggering a completion delegate - fixes soft lock when handling disconnects during login OR-26945 The client stays on the "downloading profile" screen when rejoining after disconnecting #rb josh.markiewicz, alex.fennell #tests none #R@BOMERGE-SOURCE: CL 3099782 in //Orion/Release-30.2/... via CL 3099784 via CL 3099785 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3099252 on 2016/08/24 by Lina.Halper Fixed menu text #rb: none #code review: Thomas.Sarkanen #tests: open editor and create child montage and replaced the animation Change 3099251 on 2016/08/24 by Lina.Halper Deterministic cooking of skeleton - abandon all guid from GuidMap. GuidMap is still important since we have to generate UID from it, but GuidMap only contains name once cooked #jira: UE-34834 #rb: Martin.Wilson #tests: cooking orion and make sure it works Change 3098504 on 2016/08/23 by Bart.Bressler Add server time between sending packets monitoring histogram #rb dmitry.rekman #tests ran server locally and made sure analytics events were sent Change 3098494 on 2016/08/23 by Michael.Noland Engine: Added UWorld::SetTimeUntilNextGarbageCollection to change the GC timer for use when doing automated performance capture measurements - Note: Things that force a GC will still force a GC after using this method (and they will also reset the timer) - Fixed a bug where UWorld::ForceGarbageCollection might not force a GC immediately if run on a server with no clients connected #tests Tested by calling while stat dumphitches was active and confirmed that the interval changed #codereview ben.salem, gil.gribb #rb none Change 3098491 on 2016/08/23 by Mieszko.Zielinski Expanded BTDecorator_IsAtLocation with an option to use AIDataProvider #UE4 #rb Lukasz.Furman #test golden path Change 3098070 on 2016/08/23 by Lina.Halper Fix crash with UI update reconstructing - will have to come up with a better solution than this. #rb: Martin.Wilson #tests: child anim montage Change 3097914 on 2016/08/23 by Jason.Bestimt #R@BOMERGE-AUTHOR: david.nikdel Merging CL #3097879 from //WEX/Main/Engine/Source/Runtime/Online/NotForLicensees/OnlineSubsystemMcp/... to //Orion/Main/Engine/Plugins/Online/NotForLicensees/OnlineSubsystemMcp/Source/... #Analytics #OSS: Adjusted cohort selection algorithm and test cases [CodeReviewed]: Philip.Buuck #TESTS: unit tests #RB: none #R@BOMERGE-SOURCE: CL 3097911 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3097745 on 2016/08/23 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Megre 30.2/31 @ CL 3096895 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3097716 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3097722 on 2016/08/23 by Chris.Bunner Update texture expression properties before triggering parent material recompile. #rb John.Billon #tests Editor replace references, Golden path #jira OR-27531 Change 3097694 on 2016/08/23 by Lina.Halper #Child Anim Montage - Duplicate from parent of the information it cares to get - Currently it is selective on copying what data - Modified GetAllAnimationSequencesReferred to get a partial data - Added ParentAsset/AssetMappingTable in AnimationAsset - Sequence Browser opening would also add to history - AnimNotify - CanBeplaced virtual function lets you filter which asset it's placed on #code review: Benn.Gallagher, Thomas.Sarkanen, David.Ratti #rb:Martin.Wilson #tests: creating child montage, editing, lots of UI functionality, notifies placement Change 3097513 on 2016/08/23 by Thomas.Sarkanen Non-POD structs can now be copied using the fast path We now always use CPP struct ops to perform copies when dealing with struct properties. #jira UE-34571 - Support struct member access on AnimBP fast path #rb Laurent.Delayen #tests OrionEntry with Tacticia, confirming orientation warping works correctly and fast path is enabled. Agora_P with Tacticia & bots, played two games. Change 3096729 on 2016/08/22 by Mieszko.Zielinski Fixes to EQS scoring function preview #UE4 #rb Lukasz.Furman #test golden path Change 3096596 on 2016/08/22 by Jason.Bestimt #ORION_DG - Fixes from 4.13 to video playback (CL# 3075761 & 3083970) #RB:none #Tests:none #CodeReview: matt.schembari, max.preussner #R@BOMERGE: MAIN Change 3096550 on 2016/08/22 by Jurre.deBaare Fix for HLOD dirty clusters PIE warning message #tests Simulated Origin with built HLOD clusters, and with one dirty cluster #rb none Change 3096532 on 2016/08/22 by Mieszko.Zielinski Modified GameplayTask_WaitDelay to allow specifying task's priority #UE4 As part of the change introduced UGameplayTask::NewTaskUninitialized that's basically a redirect of NewObject, but clearly indicates that a task needs to be manually initialized #codereview Lukasz.Furman #rb none #test golden path Change 3096455 on 2016/08/22 by Jason.Bestimt #R@BOMERGE-AUTHOR: keli.hlodversson #CEF: Copy upgraded CEF binaries from //Portal/Main to fix crash issues with Sofort purchases #RB David.Nikdel #TESTS none #R@BOMERGE-SOURCE: CL 3096452 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3096316 on 2016/08/22 by Sammy.James Resave to fix log warnings. BPC changes to ensure type saves. #rb #tests editor Change 3096040 on 2016/08/22 by bruce.nesbit Revised fix for landscape crash #rb GarethM #tests Game #codereview Bart.Bressler Change 3096015 on 2016/08/22 by bruce.nesbit Fixed a crash in ALandscapeProxy::PostLoad when running an editor build with -server #rb none #tests game #codereview Bart.Bressler Change 3095578 on 2016/08/19 by Mieszko.Zielinski Made NavigationSystem call TickAsyncBuild on all navigation data instances is there was an ongoing navigation build in progress in the editor #UE4 This was causing Orion's flow field to not build if auto navmesh update was disabled in the editor #rb none #test golden path #codereview Lukasz.Furman Change 3095397 on 2016/08/19 by Lina.Halper Fix issue with crash when deleting all segment #rb: Laurent.Delayen #tests: delete segment and make sure it doesn't crash #jira: UE-34830 Change 3095060 on 2016/08/19 by Bart.Bressler Don't load ULandscapeComponent objects on dedicated servers to save memory. #tests cooked server data and played a Solo vs. AI game #rb gareth.martin #codereview james.golding Change 3095037 on 2016/08/19 by Lina.Halper Potential fix with montage trigger ensure on marker sync group #jira: OR-27685 #rb: Benn.Gallagher #code review: Martin.Wilson #tests: attack primhelilx with knock up Change 3094962 on 2016/08/19 by Jason.Bestimt #R@BOMERGE-AUTHOR: david.nikdel #Merging #OSS - Added FUserOnlineAccountMcp::SelectCohort #RB: None #TESTS: test suite in source [CodeReviewed]: Philip.Buuck #R@BOMERGE-SOURCE: CL 3094961 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3094950 on 2016/08/19 by Jason.Bestimt #R@BOMERGE-AUTHOR: david.nikdel #Merge #UE4 - Made FMD5 const-correct #RB: none #TEST: none #R@BOMERGE-SOURCE: CL 3094949 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3094619 on 2016/08/19 by Lina.Halper #DUPEFIX - ANIM: SmartNAME: the cooking doesn't guarantee the package is saved in the order, so we'll still have to regenerate list without GUID. - assumed the name is all set by now #rb: Benn.Gallagher #jira : UE-34886 #tests: cooking infiltrator that showed same issue and run game. Change 3094532 on 2016/08/19 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3094498 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3094528 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3093260 on 2016/08/18 by Josh.Markiewicz #UE4 - changed how party reset occurs to skip relying on AGameState which could very rarely be null (during server travel) - removed unnecessary cast #rb bart.bressler #codereview bart.bressler, rob.cannaday #tests launched game, some basic party testing Change 3093224 on 2016/08/18 by Josh.Markiewicz #UE4 - added a chatroom class that does some basic chat room join/create/leave functionality to share between games #rb paul.moore #codereview anthony.carter #tests solo vs ai chat with 2 players, coop vs ai chat with 2 players, one leaving and rejoining Change 3092597 on 2016/08/17 by Daniel.Lamb Added Ben Crocker to the rebuild lighting emails. #rb Trivial #Test none Change 3092063 on 2016/08/17 by andrew.grant Merging using ROBO://Orion/Release-Candidate->//Orion/Main #rb #tests none Change 3091081 on 2016/08/16 by Jurre.deBaare Fixing non-Editor build errors #fix Wrapped parts in WITH_EDITOR and added IsBuilt to check if the LODActor has a valid static mesh (thus is not dirty) #tests Build Editor + Game #rb none Change 3091009 on 2016/08/16 by Mieszko.Zielinski Added a way to configure a map to not spawn AISystem instance at all #UE4 #rb none #test golden path Change 3090932 on 2016/08/16 by Michael.Noland Vixen: Added indication to the analytics and FPS charts #rb marcus.wassmer #tests Compiled for the platform Change 3090844 on 2016/08/16 by Laurent.Delayen Replicated CL 3090734 from Fortnite. --- Fix AbilitySystemComponent not ticking while playing a montage, and ticking when we're not playing a montage Here's the issue in the version of the code prior to this checkin: - UpdateShouldTick calls GetShouldTick, which checks the value of RepAnimMontageInfo.IsStopped - When we call UpdateShouldTick within AnimMontage_UpdateReplicatedData, we haven't set RepAnimMontageInfo.IsStopped yet to the correct value - So when we aren't playing any montages but are starting a new one, we were saying we shouldn't tick - It also means if we were playing a montage, and then stop, we'll start ticking - Ticking calls AnimMontage_UpdateReplicatedData, which should be called while we're playing #codereview john.abercrombie #rb none #tests golden path Change 3090832 on 2016/08/16 by Michael.Noland Windows: Fixed a whitespace issue #rb none #tests Compiled for windows Change 3090688 on 2016/08/16 by Jason.Bestimt #R@BOMERGE-AUTHOR: andrew.grant Merging using ROBO://Orion/Release-Candidate->//Orion/Main #rb none #tests built #R@BOMERGE-SOURCE: CL 3090687 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3090547 on 2016/08/16 by Jurre.deBaare Need a warning message similar to lighting unbuilt when HLOD cluster is not built #fix Added HLOD clusters need to be rebuilt message similar to the lighting one during PIE and game-time, and cleaned/changed "'DisableAllScreenMessages' to suppress" behaviour #jira UE-34335 #rb none #codereview Michael.Noland #tests pie Agora with and without dirty HLOD clusters Change 3090285 on 2016/08/16 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3090267 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3090282 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3089413 on 2016/08/15 by paul.mader Agora 2.0 assets Change 3089266 on 2016/08/15 by Max.Chen Sequencer: Add Convert to Possessable Copy from Dev-Sequencer #jira UE-32139 #rb none #tests Convert steel to possessable in Gameplay_PS4 map. Change 3089136 on 2016/08/15 by Mieszko.Zielinski Fixed AISense_Sight's time slicing unintentionally skipping queue aging if given time limit is reached #UE4 #rb Lukasz.Furman #codereview Dan.Youhon #test golden path Change 3089118 on 2016/08/15 by Mieszko.Zielinski Fixed a rare crash in UBlackboardData::GetKeyType resulting from a key selector referencing a type that has been removed from the project's source code #UE4 #rb none #test golden path Change 3088976 on 2016/08/15 by Andrew.Grant Fixed issue with PS4 toolchain ignoring ModuleRules.CodeOptimization.Never / ModuleRules.CodeOptimization.Always when determining optimization level of modules. Fixed issue with VC toolchain ignoring ModuleRules.CodeOptimization.Never setting. Removed superflous /Os from VC debugg settings #rb none #tests verified module built with 'Never' on PS4/Win is built without optimizations. #codereview Marcus.Wassmer, Ben.Marsh Change 3088830 on 2016/08/15 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3088807 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3088829 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3088597 on 2016/08/14 by Jason.Bestimt #ORION_DG - Trying to resolve R@BOMERGE collision (DUI to MAIN -> DG) #RB:none #Tests:none #CodeReview: andrew.grant, david.ratti, matt.schembari Change 3087827 on 2016/08/12 by Bart.Bressler Updates to skeletal mesh memory saving on dedicated server #rb lina.halper #tests Cooked server data, played a game for a while in Solo vs. AI Change 3087351 on 2016/08/12 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) #jira OR-27406 #rb RyanG #tests Replays Change 3087118 on 2016/08/12 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3086747 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3087117 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3086176 on 2016/08/11 by Marcus.Wassmer Fix PS4 ShaderPipelines not matching pixel/vertex shader properly. #rb Rolando.Caloca #tests Broken PS4 content before/after Change 3085992 on 2016/08/11 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Unclog R@BOMERGE #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3085987 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3085911 on 2016/08/11 by Laurent.Delayen Added FBoneContainer::BoneIsChildOf for FCompactPoseBoneIndex #rb none #tests Orientation Warping Change 3085614 on 2016/08/11 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3085547 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3085598 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3084507 on 2016/08/10 by Marcus.Wassmer Duplicate 3070376 and 3078879 to fix corrupted decals on Vixen. #rb none #tests paragon ps4/vixen #codereview Olaf.Piesche Change 3084136 on 2016/08/10 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3083799 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3083814 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3083424 on 2016/08/09 by Max.Chen Sequence Recorder: Fix crash when actor class to record is null. #tests Use sequence recorder to record a skeletal mesh actor #rb none Change 3083134 on 2016/08/09 by Jason.Bestimt #R@BOMERGE-AUTHOR: sam.zamani #online,store,ps4 - creating one offer entry per entitlement #rb david.nikdel, ian.fox #tests MTX purhcase on PS4 #lockdown: andrew.grant #R@BOMERGE-SOURCE: CL 3083127 in //Orion/Release-30.1/... via CL 3083128 via CL 3083131 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3083069 on 2016/08/09 by Marcus.Wassmer Vixen scalability changes #rb Michael.Noland #tests vixen/ps4 #codereview jordan.walker Change 3083063 on 2016/08/09 by Marcus.Wassmer Most games will probably run out of memory if setup to do auto-4k. Make this a setting that's off by default. #rb Michael.Noland #codereview Luke.Thatcher, Lee.Clark #tests vixen on 4k. Change 3082778 on 2016/08/09 by Marcus.Wassmer Duplicate fix for Vixen GPU page faults and rendertarget errors (3066087) #rb none #tests Agora on vixen. Change 3082772 on 2016/08/09 by Marcus.Wassmer Duplicate fix for detail mode reregistration (3065543) #rb none #tests Toggled detail mode, observe proper items spawning Change 3082765 on 2016/08/09 by Marcus.Wassmer Don't crash when trying to use windowed vsync on vixen #rb Michael.Noland #test ran paragon on vixen #codereview Luke.Thatcher,Lee.Clark Change 3082764 on 2016/08/09 by Marcus.Wassmer fix HLOD distance scale not working properly when components are re-registered. #rb michael.noland #codereview jurre.debarre #tests setting multiple times, setting on boot via deviceprofile Change 3082429 on 2016/08/09 by Jason.Bestimt #R@BOMERGE-AUTHOR: sam.zamani Merging //Orion/Release-30.1 to Main (//Orion/Main) Change: 3082419 #online,store,PS4 OR-25384 [PS4] "There is no content. It might not be for sale yet, or might no longer be for sale" at main menu and at post match screen - added config option for toggling store on PS4 [OnlineSubsystemPS4] bStoreEnabled=true - can also override via title specific json values in <titleid>\title.json allow_mtx=true [CodeReviewed]: andrew.grant, phillip.buck, ian.fox #lockdown: andrew.grant #rb none #tests ps4 run with titleid=CUSA3609_00 (which has mtx disabled for PS4 since that title has no store support) #R@BOMERGE-SOURCE: CL 3082428 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3082194 on 2016/08/09 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3082105 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3082192 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3080984 on 2016/08/08 by Lina.Halper Issue with not being able to set static animation data via BP - artists were using SetAnimation/PlayAnimation, but they are not safe to be used in construction script, so made sure the other serializable properties are exposed via BP - also since they want it to work in level viewport, I have to tick/refresh whenever it's getting called. #rb: Martin.Wilson #tests: Sword Beauty map Change 3080665 on 2016/08/08 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3080081 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3080543 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3080565 on 2016/08/08 by Laurent.Delayen Fix for curve values during URO interpolation. Fixes flashing of materials and Twinblast's ult weapon. https://jira.ol.epicgames.net/browse/OR-27107 https://jira.ol.epicgames.net/browse/OR-24358 #rb lina.halper, martin.wilson #tests Twinblast's ult and Coil's primary. Change 3079832 on 2016/08/05 by Jason.Bestimt #R@BOMERGE-AUTHOR: marcus.wassmer Fix for PS4 crash reports not attaching the minidump when trying to force full crash dumps via commandline #rb none [CodeReviewed] Chris.Wood #tests checked crashcontext on PC/PS4 #lockdown Andrew.Grant #R@BOMERGE-SOURCE: CL 3078933 in //Orion/Release-30/... via CL 3078934 via CL 3078935 via CL 3079831 #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3079045 on 2016/08/05 by Lina.Halper Adding more log to figure out why ActivePlayers.Count becomes inconsistent. #rb: Martin.Wilson #tests: PIE with bots Change 3078944 on 2016/08/05 by Rolando.Caloca O - Update blacklisted driver #jira OR-27051 #rb Marcus.Wassmer #tests Run with AMD card Change 3078735 on 2016/08/05 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3078670 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3078734 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3078122 on 2016/08/04 by Dmitry.Rekman Linux: treat abort() / SIGABRT as crash. - Rationale: certain code not under our control (most notably, stack smashing protector) may call abort(), which would previously terminate the engine without any chance to even enter the crash handler. - Rewrote RequestExit() because it used abort() itself. - Also removed -fstack-protector. The logic behind this is: stack protector calls abort() on detecting a smash (which is suspected to contribute to missing reports), but does it at an inappropriate place, that causes stack unwinding to crash later. As bad as it sounds, it may be better to allow stack to be corrupted and crash later - hopefully outside of libc code - to some other reason. #rb Mark.Satterthwaite #codereview Mark.Satterthwaite, Michael.Noland, Andrew.Grant #review-3078104 @Mark.Satterthwaite, @Michael.Noland, @Andrew.Grant #tests Ran Linux server, crashed using different methods. Change 3077887 on 2016/08/04 by Dmitry.Rekman Initialize StackCount to 0 (kills valgrind warning). #rb David.Ratti #codereview David.Ratti #tests Ran Linux server. Change 3077257 on 2016/08/04 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3077193 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3077256 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3077242 on 2016/08/04 by Dmitry.Rekman Linux: stop heartbeat thread before handling the crash. #rb Robert.Manuszewski #codereview Robert.Manuszewski, Andrew.Grant #tests Compiled and ran Linux server, crashed it. Change 3076676 on 2016/08/03 by Dmitry.Rekman Linux: print details about memory access (read or write). - Also print all the 16 digits of the pointer. - Read/write detection only implemented for x86_64. #rb Andrew.Grant #codereview Andrew.Grant #tests Compiled (natively) and ran Linux server. Change 3076675 on 2016/08/03 by Dmitry.Rekman Print a bit more info about the array in assert. #rb Andrew.Grant #codereview Andrew.Grant #test Compiled and ran Linux server. Change3076010on 2016/08/03 by Laurent.Delayen Moved OrionAnimNode_LegIK from Paragon to Engine. #codereview lina.halper #rb none #tests Grim.exe + Iggy & Scorch Change 3075512 on 2016/08/03 by Matt.Kuhlenschmidt Reimplemented 3070766 for Orion: Make sure richtooltips are not generated for hidden enum items so that there is not a mismatch between rich tooltips and enum items (causing a crash) #rb none #tests none Change 3075446 on 2016/08/03 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3075422 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3075445 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3075394 on 2016/08/03 by HaarmPieter.Duiker Adding a shadows max and highlights min parameters to allow the user to control when the 'shadows' controls fall off and when the 'highlights' controls ramp in. #rb marcus.wassmer #tests post process color correction Change 3074314 on 2016/08/02 by Dmitry.Rekman Linux: change optimization from -O2 to -O1 (temporarily?). - The purpose is to make callstacks easier to follow and possibly catch stack smashing (if it happens) earlier. - Also adds a line to UBT output during compilation to draw attention. #rb Michael.Noland #codereview Michael.Noland, Andrew.Grant, Bart.Bressler #tests Compiled and ran Linux server. Change 3073553 on 2016/08/02 by jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3073360 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3073481 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) #R@BOMERGE-SAYS: Beep boop! I couldn't merge this change. Please do it yourself, human. //Orion/Dev-General/OrionGame/Content/Characters/Heroes/BP_Hero.uasset - can't integrate exclusive file already opened #CodeReview: jason.bestimt Change 3073505 on 2016/08/02 by Daniel.Lamb Added cook modification delegate stats to cooker stats. #rb Wes.Hunt #test cook paragon. Change 3072440 on 2016/08/01 by Aaron.Eady PlayerController Force Feedback (Debug only); Adding #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) around the use of the debug only variable ForceFeedbackEffectHistoryEntries. #rb none #tests SHIPPING Change 3072259 on 2016/08/01 by Aaron.Eady PlayerController Force Feedback (Debug only); Added more information to the things displayed on the screen for force feedback when we do ShowDebug ForceFeedback. #rb Michael.Noland #tests PIE Change 3071908 on 2016/08/01 by John.Pollard Fix null reference crash #rb DavidR #tests Live game + replays Change 3071876 on 2016/08/01 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) Assertion failed: WriterState.Changed.Num() == 0 occurs when a Pitcher Husk hits the Player #rb none #tests FN + Paragon live game + replays #codereview Andrew.Grant Change 3071875 on 2016/08/01 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) Finalize replay version system * No longer use changelist to filter replays (so we will only filter by engine/game version now, which need to be hand cranked to invalidate old versions) * Submit actual changelist when uploading (rather than locking to previous versions). We can do this now since we don't filter by changelist anymore. * Removed unnecessary 'bShowAllVersions' property from replay browser code, using cvar instead (orion.ShowAllReplayVersions) #rb RyanG #tests Live game + replays #codereview Andrew.Grant Change 3071874 on 2016/08/01 by John.Pollard Merging using Dev-Networking_->_Dev-General_(Orion) Fix gameplay tags to work better with backwards compatibility in replays * We use the net field export group system in the package map to export tag names as a packed index * This will allow us to see the names of tags that no longer exists on the remote side #rb RyanG #tests Live game + replays #codereview Andrew.Grant Change 3071776 on 2016/08/01 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30.2 @ CL 3071738 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3071775 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3071258 on 2016/07/30 by Aaron.McLeran OR-26580 CRASH: FXAudio2SoundSource::GetChannelVolumes - Silent Crash during gameplay - Removed voice operation set since it was causing crashes when stopping voices. Still a good idea, but need to make sure the async OnBufferEnd and stopping an FSoundSource can work together. - Added a proxy object that wraps the FAsyncTask used for async decoding. Calling IsDone() and EnsureCompletion() can't happen at the same time from different threads now. #rb none #tests ran paragon soaking for a long time with constant AI combat and observed no crashes or audio issues. Change 3071099 on 2016/07/30 by Aaron.McLeran OR-26580 CRASH: FXAudio2SoundSource::GetChannelVolumes - Silent Crash during gameplay - Temporary revert of a portion of CL 3067560 which exacerbates an issue with the async decoding tasks and calling IsDone and EnsureComplete on different threads. #rb none #tests ran paragon with change and noticed no change in audio quality Change 3070916 on 2016/07/29 by Andrew.Grant Missed file! #rb #tests na Change 3070915 on 2016/07/29 by Andrew.Grant Merging //UE4/Main @ 3070724 through //UE4/Orion-Staging #rb none #tests Engine QA, Orion QA smoke Change 3070576 on 2016/07/29 by Uriel.Doyon Fixed initialization of the defrag pool size. Now controlled by r.PS4DefragPoolSize. #review-3070386 @marcus.wassmer #jira OR-25941 #rb marcus.wassmer #tests Run Game on PS4, and in editor Change 3070086 on 2016/07/29 by Martin.Wilson Fixed ensure triggering during sequencer playback due to double update. #jira UE-33938 #rb Thomas.Sarkanen #tests opened affected asset and verified problem no longer occured Change 3070016 on 2016/07/29 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 30 @ CL 3069935 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3069976 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3069435 on 2016/07/28 by Ian.Fox #Orion, #Mcp - Check if Price Engine is configured before attempting query #rb Sam.Zamani #tests none #codereview Sam.Zamani Change3069381on 2016/07/28 by Michael.Noland Animation: Demoted a check() in anim sync group code to an ensure() to unblock others #rb nick.penwarden #tests Loaded Paragon cine asset that was crashing #codereview lina.halper, martin.wilson Change 3069203 on 2016/07/28 by Dmitry.Rekman Headless client: do not draw windows. - Disables a bunch of code, including reaching into font cache to estimate width. - Should be probably disabled on a higher level, but cutting out the whole Slate application is infeasible (according to BradA/BenM, due to some logic requiring widgets). #rb Nick.Atamas #review-3068983 @Nick.Atamas, @Michael.Noland, @Brad.Angelcyk, @Ben.Salem #codereview Nick.Atamas, Michael.Noland, Brad.Angelcyk, Ben.Salem #tests Compiled and ran Orion Linux client. Change 3069181 on 2016/07/28 by Lina.Halper Fix struct redirector for Orion anim node moving to engine #rb: Maciej.Mroz #code review:Laurent.Delayen #tests: editor loading the anim BP that caused the name conversion Change 3069092 on 2016/07/28 by Aaron.McLeran OR-26161 Client hitches indefinitely when using Stat soundcues / soundwaves - Not all active sounds have sound classes, was causing a crash #codereview marc.audy #rb zabir.hoque #tests Run game with stat soundcues and not crash Change 3068969 on 2016/07/28 by David.Ratti Move test for invalid gameplaycue instance up, since calling IsPendingKill() on garbage can cause crash too. #rb none #tests compile Change 3068902 on 2016/07/28 by David.Ratti Code for tracking down UGameplayCueManager::GetInstancedCueActor crash. #rb none #tests compile Change 3068831 on 2016/07/28 by Aaron.McLeran OR-26417 Reverb is too loud in-game in Dev-General - Initializing prev reverb to 0s so that the first default reverb gets set when no audio volume is set. #rb Jeff.Campeau #tests run a map with no reverb audio volume and reverb is not super wet Change 3068529 on 2016/07/28 by Jason.Bestimt #R@BOMERGE-AUTHOR: david.nikdel #OSS #PurchaseMcp: Use GameService->CreateOnlineHttpRequest instead of McpSubsystem->CreateRequest to query receipts (uses subsystem config) #RB: none #TESTS: none #R@BOMERGE-SOURCE: CL 3068465 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3068399 on 2016/07/28 by Andrew.Rodham Sequencer: Changed animation tracks to allow more animation types (such as anim montages) - APIs now accept UAnimSequenceBases rather than UAnimSequences to afford more flexibility #jira OR-25769 #tests Tested all combinations of animation with sequencer (montage+sequence on asset/BP driven animation components) and matinee. Tested running a game and playing back the announce trailer. Rendered out some movies to ensure that trails work correctly. #rb Benn.Gallagher Change 3068138 on 2016/07/28 by Marcus.Wassmer Disable mallocleak testing by default #rb none #test none Change 3068121 on 2016/07/28 by Marcus.Wassmer Make sure we always do fast stack captures when USE_FAST_STACKTRACE is enabled. Fixes game becoming unresponsive on Windows after 'mallocleak' dumps data. Any other tool that uses stacktraces could become 700 - 1000x slower after any stack symbolication also. #rb Robert.Manuszewski #tests stack tracing / symbolication with mallocleak on windows. Change 3068119 on 2016/07/28 by Marcus.Wassmer Fix MallocLeakProxy deadlock #rb Robert.Manuszewski #tests mallocleak start/stop/dump on windows Change 3067752 on 2016/07/27 by Michael.Noland Engine: Refactored FPS chart creation to make it modular so many performance data consumers can be active at once, allowing greater flexibility and decoupling game analytics from FPS chart exec commands - IPerformanceDataConsumer is an interface for all consumers of per-frame performance tracking data, and instances can be registered/unregisted with the engine using AddPerformanceDataConsumer/RemovePerformanceDataConsumer - The implementation of the 'standard' frame time and hitch histogram tracking is FPerformanceTrackingChart, while the per-frame logging .csv is split into a separate FFineGrainedPerformanceTracker class. - The calculation of frame time breakdowns and hitch detection now occur as long as at least one IPerformanceDataConsumer is registered - Internally the code has been cleaned up a bit to use FHistogram for data storage instead of custom binning code Upgrade Notes: - DumpFPSChartAnalytics has been removed, games that used it should switch to creating their own instance of FPerformanceTrackingChart and call DumpChartToAnalyticsParams on it directly - In general games should have no reason to programmatically call GEngine->StartFPSChart anymore, instead creating their own instance (this prevents conflicts when using the engine console commands) - HTML output for stopfpschart is now generated to a single file rather than two duplicate files (using both map name and capture time as part of the file name) - Removed PauseFPSChart, IsFPSChartActive, and GetFPSChartBoundByFrameCounts to reflect that the GEngine instances aren't meant for external use (Start/Stop are left public for automated testing that wants to use them to do logging, but may also be moved private in the future) Paragon: - Updated to use a separate FPerformanceTrackingChart for gameplay versus in-game menus and removed the duplicated code and GameThreadHitchChart event - Removed partial USE_SERVER_PERF_COUNTERS code in ChartCreation.cpp, splitting it out into a separate observer, which currently lives in Paragon but will be moved to shared code in a separate checkin. The code was only useful in the first place along with other Paragon-side code that was consuming it. #rb dmitry.rekman #codereview bob.tellez, peter.knepley, andrew.grant, john.mauney #review-3067607 @Dmitry.Rekman, @Bob.Tellez #tests Tested manual startfpschart/stopfpschart as well as Paragon match analytics via golden path solo vs AI Change 3067654 on 2016/07/27 by Michael.Noland FString - Fix divide overload path concatenation for empty paths since there are several places in the engine that expect using that doing { path / "" } will append a / onto path. #rb steve.robb #jira UE-31959 [duplicating CL# 3039827] #tests Tried moving a folder in the editor Change 3067644 on 2016/07/27 by Aaron.McLeran OR-24537 Looping audio sometimes persists in Agora Adding stopping sounds if audio component is destroyed while playing a looping sound #rb jeff.campeau #tests audio component stops looping sound if audio component is destroyed prematurely Change 3067560 on 2016/07/27 by Aaron.McLeran OR-26322 Client Hang in FXAudio2EffectsManager::SetReverbEffectParameters - Only applying reverb parameters if they've changed from previous reverb params to avoid unnecessarily spamming the XAudio2 API call - using xaudio2 operation sets to ensure that voice and effect params are executing in sequence - only calling destroy voice after all voice and effect changes have been committed to avoid destroy voice interfering with those commands - Don't call EnsureCompletion on pending async tasks on teardown #rb Jeff.Campeau #tests play paragon with change, notice no changes to audio behavior, no crashes. Created testmap with several reverb zones and demonstrated reverb effect transitions Change 3067420 on 2016/07/27 by jason.bestimt #ORION_MAIN - Merge 29.2/30 @ CL 3067312 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3067400 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3067316 on 2016/07/27 by jason.bestimt #ORION_MAIN - Merge DUI @ CL 3065602 #RB:none #Tests:none [CodeReviewed]: matt.schembari #R@BOMERGE-SOURCE: CL 3067079 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change3067025on 2016/07/27 by Michael.Noland Core: Corrected the initial value of FLightweightTimeGuard::FrameTimeThresholdMS to be in MS rather than seconds and did a few coding standards fixes #rb none #tests Compiled Change 3067020 on 2016/07/27 by Michael.Noland Core: Various improvements to FHistogram and split it out into separate files - Added the ability to use a separate thresholding key than the actual measurement value being recorded (e.g., when accumulating frame time spent in a chart keyed on framerate) - Added O(1) getters for total sample counts and sum of all measurements - Removed encapsulation-breaking SetBinCountByIndex / SetBinSumByIndex - Added support for specifying explicit histogram bucket thresholds #rb dmitry.rekman #tests Tested with another pending changelist that moves FPS charts to use FHistogram for the underlying storage Change 3066681 on 2016/07/27 by Frank.Gigliotti Camera anim field of view fix; * The FOV is now reset on the PlayerCameraManager camera actor when it's initialized. This fixes cases of stale FOV values after playing camera anims that don't end with the FOV at it's base value. * Base FOV can now be edited in the CameraAnim properties. This allows you to specify what the FOV keys are relative to. Previously it was always using a base FOV of 90 degrees. #RB None #CodeReview Jeff.Farris #Tests Multiple camera animations in PIE Change 3066508 on 2016/07/27 by Lina.Halper Smartname guid will be discarded during cooking, and once it's cooked, it's trusted to have correct name. #code review:Martin.Wilson, Benn.Gallagher #rb: Martin.Wilson #tests: cooked test map, run test map, PIE, saving content, loading standalone game Change 3066246 on 2016/07/27 by Jason.Bestimt #R@BOMERGE-AUTHOR: andrew.grant Fix for non-unity error #rb none #tests compiled #R@BOMERGE-SOURCE: CL 3066245 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3066167 on 2016/07/27 by Benn.Gallagher Fixed clothing corruption seen on Twinblast after mesh updates. We were copying a u32 index buffer into a multisize container but CopyIndexBuffer doesn't change the data size when copying - only when rebuilding. #rb Ori.Cohen #tests Editor, PIE, Applying clothing to characters. Change 3065868 on 2016/07/27 by Michael.Noland Blueprints: Fixing non-editor build (missing WITH_EDITOR) #rb none #tests Compiled PS4 Change 3065749 on 2016/07/26 by Michael.Noland Blueprints: Prevent a crash on load in RemoveNodeAndPromoteChildren when removing a corrupted SCS node if it has no parent link (the children are moved to the root node instead) #codereview mike.beach, marc.audy #tests Loaded and recovered a corrupted Blueprint on Cameron's machine #rb Phillip.Kavan Change 3065706 on 2016/07/26 by Josh.Markiewicz #UE4 - changed default values for bLogoutOnSessionTimeout for reservation beacons - fixed non shipping cmd line override to be correct #rb none #codereview andrew.grant, paul.moore #tests none Change 3065359 on 2016/07/26 by Rob.Cannaday Limit external id querying to 100 ids per call. The backend currently enforces this and is returning an error when we exceed this limit. Break up calls in batches of 100 ids. #jira OR-20674 #rb ian.fox #tests login to front end with PC, PS4. forced tests to simulate > 100 requests. Change 3065197 on 2016/07/26 by Bart.Bressler Change how PS4 sessions work: - We now will only try to join somebody's PS4 session only if we accepted an invite from the PS4 system software. This means that an MCP party can have members in different PS4 sessions. - Refactored a lot of the delegates in UOrionParty to lambdas to try to make it more readable - Added comments, other misc. code cleanup. #rb josh.markiewicz, sam.zamani, rob.cannaday #tests created cross play parties with multiple pc + ps4 players #jira OR-20332 Change 3065158 on 2016/07/26 by Lina.Halper Fix the guid keep generated by adding to the database. - This caused worse problem with non-deterministic cooking - This doesn't fix UE-33454 for 100%, but this was the main reason why this was so visible #rb: Martin.Wilson #jira: UE-33772, UE-33454 #tests: cooked AI_Test map, editor rename curves Change 3064735 on 2016/07/26 by Dmitry.Rekman Linux: added WebRTC libs. - Compiled against glibc 2.12 / CentOS 6.x environment (see howto in a separate doc). #rb none #tests Tested OrionClient in Dev-General, and UE4Editor in Dev-Platform. (Edigrating 3063715 from //UE4/Dev-Platform/... to //Orion/Dev-General/...) Change 3064727 on 2016/07/26 by Dmitry.Rekman Fix crash on cooker exit (UE-33583). - Global/static tickable objects could outlive the collection and trigger asserts when removing themselves from it. #rb none #tests Compiled and ran Linux server and Linux client. (Edigrating 3058779 from //UE4/Dev-Platform/... to //Orion/Dev-General/...) Change 3064725 on 2016/07/26 by Dmitry.Rekman Linux: use libc++ instead of libstdc++. - Needed to solve problems with third-party C++ libraries (e.g. WebRTC). - Bundled libc++ 3.8.1 (TPS cleared). - Turned off ICU compilation (needs recompile against libc++). - Some libraries (e.g. FBX sdk) still need libstdc++, so in practice it is going to be a mix. #rb none #tests Built and ran a number of Linux targets. (Edigrating3057152from //UE4/Dev-Platform/... to //Orion/Dev-General/...) Change 3064572 on 2016/07/26 by Jason.Bestimt #R@BOMERGE-AUTHOR: jason.bestimt #ORION_MAIN - Merge 29.2 @ CL 3064545 #RB:none #Tests:none #R@BOMERGE-SOURCE: CL 3064569 in //Orion/Main/... #R@BOMERGE-BOT: ORION (Main -> Dev-General) Change 3064523 on 2016/07/26 by Jon.Lietz Fixing it so gameplay effects with execution none will no longer keep the BP in a dirty state. Only call EmptyArray() on CalculationModifiersArrayPropHandle if it has any elements. #RB none #tests BP compiles and stays not dirty #codereview dave.ratti@epicgames.com [CL 3111290 by Andrew Grant in Main branch]
3217 lines
92 KiB
C++
3217 lines
92 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PakFilePrivatePCH.h"
|
|
#include "IPlatformFilePak.h"
|
|
#include "SecureHash.h"
|
|
#include "FileManagerGeneric.h"
|
|
#include "ModuleManager.h"
|
|
#include "IPlatformFileModule.h"
|
|
#include "IOBase.h"
|
|
#include "BigInt.h"
|
|
#include "SignedArchiveReader.h"
|
|
#include "PublicKey.inl"
|
|
#include "AES.h"
|
|
#include "GenericPlatformChunkInstall.h"
|
|
#include "AsyncFileHandle.h"
|
|
|
|
DEFINE_LOG_CATEGORY(LogPakFile);
|
|
|
|
DEFINE_STAT(STAT_PakFile_Read);
|
|
DEFINE_STAT(STAT_PakFile_NumOpenHandles);
|
|
|
|
FFilenameSecurityDelegate& FPakPlatformFile::GetFilenameSecurityDelegate()
|
|
{
|
|
static FFilenameSecurityDelegate Delegate;
|
|
return Delegate;
|
|
}
|
|
|
|
#if USE_NEW_ASYNC_IO
|
|
#define USE_PAK_PRECACHE (1) // you can turn this off to use the async IO stuff without the precache
|
|
#else
|
|
#define USE_PAK_PRECACHE (0)
|
|
#endif
|
|
|
|
/**
|
|
* Precaching
|
|
*/
|
|
|
|
#if USE_PAK_PRECACHE
|
|
#include "TaskGraphInterfaces.h"
|
|
|
|
|
|
#define PAK_CACHE_GRANULARITY (64 * 1024)
|
|
#define PAK_CACHE_MAX_REQUEST (1024 * 1024)
|
|
#define PAK_CACHE_MAX_REQUESTS (8)
|
|
#define PAK_CACHE_MAX_PRIORITY_DIFFERENCE_MERGE (AIOP_Normal - AIOP_Precache)
|
|
|
|
DECLARE_MEMORY_STAT(TEXT("PakCache Total"), STAT_PakCacheMem, STATGROUP_Memory);
|
|
|
|
static int32 GPakCache_Enable = 1;
|
|
static FAutoConsoleVariableRef CVar_Enable(
|
|
TEXT("pakcache.Enable"),
|
|
GPakCache_Enable,
|
|
TEXT("If > 0, then enable the pak cache.")
|
|
);
|
|
|
|
static int32 GPakCache_MaxRequestsToLowerLevel = 2;
|
|
static FAutoConsoleVariableRef CVar_MaxRequestsToLowerLevel(
|
|
TEXT("pakcache.MaxRequestsToLowerLevel"),
|
|
GPakCache_MaxRequestsToLowerLevel,
|
|
TEXT("Controls the maximum number of IO requests submitted to the OS filesystem at one time. Limited by PAK_CACHE_MAX_REQUESTS.")
|
|
);
|
|
|
|
static int32 GPakCache_MaxRequestSizeToLowerLevelKB = PAK_CACHE_MAX_REQUEST / 1024;
|
|
static FAutoConsoleVariableRef CVar_MaxRequestSizeToLowerLevelKB(
|
|
TEXT("pakcache.MaxRequestSizeToLowerLevellKB"),
|
|
GPakCache_MaxRequestSizeToLowerLevelKB,
|
|
TEXT("Controls the maximum size (in KB) of IO requests submitted to the OS filesystem. Limited by PAK_CACHE_MAX_REQUEST.")
|
|
);
|
|
|
|
static int32 GPakCache_MaxPrecacheMemMB = 0;
|
|
static FAutoConsoleVariableRef CVar_MaxPrecacheMemMB(
|
|
TEXT("pakcache.MaxPrecacheMemMB"),
|
|
GPakCache_MaxPrecacheMemMB,
|
|
TEXT("Controls the maximum amount of memory (in MB) that will be used for precaching. Total memory use can be higher because not all requests are precache requests. Setting this to zero means unlimited memory.")
|
|
);
|
|
|
|
|
|
|
|
class FPakPrecacher;
|
|
|
|
typedef uint64 FJoinedOffsetAndPakIndex;
|
|
static FORCEINLINE uint16 GetRequestPakIndexLow(FJoinedOffsetAndPakIndex Joined)
|
|
{
|
|
return uint16((Joined >> 48) & 0xffff);
|
|
}
|
|
|
|
static FORCEINLINE int64 GetRequestOffset(FJoinedOffsetAndPakIndex Joined)
|
|
{
|
|
return int64(Joined & 0xffffffffffffll);
|
|
}
|
|
|
|
static FORCEINLINE FJoinedOffsetAndPakIndex MakeJoinedRequest(uint16 PakIndex, int64 Offset)
|
|
{
|
|
check(Offset >= 0);
|
|
return (FJoinedOffsetAndPakIndex(PakIndex) << 48) | Offset;
|
|
}
|
|
|
|
enum
|
|
{
|
|
IntervalTreeInvalidIndex = 0
|
|
};
|
|
|
|
|
|
typedef uint32 TIntervalTreeIndex; // this is the arg type of TSparseArray::operator[]
|
|
|
|
static uint32 GNextSalt = 1;
|
|
|
|
// This is like TSparseArray, only a bit safer and I needed some restrictions on resizing.
|
|
template<class TItem>
|
|
class TIntervalTreeAllocator
|
|
{
|
|
TArray<TItem> Items;
|
|
TArray<int32> FreeItems; //@todo make this into a linked list through the existing items
|
|
uint32 Salt;
|
|
uint32 SaltMask;
|
|
public:
|
|
TIntervalTreeAllocator()
|
|
{
|
|
check(GNextSalt < 4);
|
|
Salt = (GNextSalt++) << 30;
|
|
SaltMask = MAX_uint32 << 30;
|
|
verify((Alloc() & ~SaltMask) == IntervalTreeInvalidIndex); // we want this to always have element zero so we can figure out an index from a pointer
|
|
}
|
|
inline TIntervalTreeIndex Alloc()
|
|
{
|
|
int32 Result;
|
|
if (FreeItems.Num())
|
|
{
|
|
Result = FreeItems.Pop();
|
|
}
|
|
else
|
|
{
|
|
Result = Items.Num();
|
|
Items.AddUninitialized();
|
|
|
|
}
|
|
new ((void*)&Items[Result]) TItem();
|
|
return Result | Salt;;
|
|
}
|
|
void EnsureNoRealloc(int32 NeededNewNum)
|
|
{
|
|
if (FreeItems.Num() + Items.GetSlack() < NeededNewNum)
|
|
{
|
|
Items.Reserve(Items.Num() + NeededNewNum);
|
|
}
|
|
}
|
|
FORCEINLINE TItem& Get(TIntervalTreeIndex InIndex)
|
|
{
|
|
TIntervalTreeIndex Index = InIndex & ~SaltMask;
|
|
check((InIndex & SaltMask) == Salt && Index != IntervalTreeInvalidIndex && Index >= 0 && Index < Items.Num() && !FreeItems.Contains(Index));
|
|
return Items[Index];
|
|
}
|
|
FORCEINLINE void Free(TIntervalTreeIndex InIndex)
|
|
{
|
|
TIntervalTreeIndex Index = InIndex & ~SaltMask;
|
|
check((InIndex & SaltMask) == Salt && Index != IntervalTreeInvalidIndex && Index >= 0 && Index < Items.Num() && !FreeItems.Contains(Index));
|
|
Items[Index].~TItem();
|
|
FreeItems.Push(Index);
|
|
if (FreeItems.Num() + 1 == Items.Num())
|
|
{
|
|
// get rid everything to restore memory coherence
|
|
Items.Empty();
|
|
FreeItems.Empty();
|
|
verify((Alloc() & ~SaltMask) == IntervalTreeInvalidIndex); // we want this to always have element zero so we can figure out an index from a pointer
|
|
}
|
|
}
|
|
FORCEINLINE void CheckIndex(TIntervalTreeIndex InIndex)
|
|
{
|
|
TIntervalTreeIndex Index = InIndex & ~SaltMask;
|
|
check((InIndex & SaltMask) == Salt && Index != IntervalTreeInvalidIndex && Index >= 0 && Index < Items.Num() && !FreeItems.Contains(Index));
|
|
}
|
|
};
|
|
|
|
class FIntervalTreeNode
|
|
{
|
|
public:
|
|
TIntervalTreeIndex LeftChildOrRootOfLeftList;
|
|
TIntervalTreeIndex RootOfOnList;
|
|
TIntervalTreeIndex RightChildOrRootOfRightList;
|
|
|
|
FIntervalTreeNode()
|
|
: LeftChildOrRootOfLeftList(IntervalTreeInvalidIndex)
|
|
, RootOfOnList(IntervalTreeInvalidIndex)
|
|
, RightChildOrRootOfRightList(IntervalTreeInvalidIndex)
|
|
{
|
|
}
|
|
~FIntervalTreeNode()
|
|
{
|
|
check(LeftChildOrRootOfLeftList == IntervalTreeInvalidIndex && RootOfOnList == IntervalTreeInvalidIndex && RightChildOrRootOfRightList == IntervalTreeInvalidIndex); // this routine does not handle recursive destruction
|
|
}
|
|
};
|
|
|
|
static TIntervalTreeAllocator<FIntervalTreeNode> GIntervalTreeNodeNodeAllocator;
|
|
|
|
static FORCEINLINE uint64 HighBit(uint64 x)
|
|
{
|
|
return x & (1ull << 63);
|
|
}
|
|
|
|
static FORCEINLINE bool IntervalsIntersect(uint64 Min1, uint64 Max1, uint64 Min2, uint64 Max2)
|
|
{
|
|
return !(Max2 < Min1 || Max1 < Min2);
|
|
}
|
|
|
|
template<typename TItem>
|
|
// this routine assume that the pointers remain valid even though we are reallocating
|
|
static void AddToIntervalTree_Dangerous(
|
|
TIntervalTreeIndex* RootNode,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
TIntervalTreeIndex Index,
|
|
uint64 MinInterval,
|
|
uint64 MaxInterval,
|
|
uint32 CurrentShift,
|
|
uint32 MaxShift
|
|
)
|
|
{
|
|
while (true)
|
|
{
|
|
if (*RootNode == IntervalTreeInvalidIndex)
|
|
{
|
|
*RootNode = GIntervalTreeNodeNodeAllocator.Alloc();
|
|
}
|
|
|
|
int64 MinShifted = HighBit(MinInterval << CurrentShift);
|
|
int64 MaxShifted = HighBit(MaxInterval << CurrentShift);
|
|
FIntervalTreeNode& Root = GIntervalTreeNodeNodeAllocator.Get(*RootNode);
|
|
|
|
if (MinShifted == MaxShifted && CurrentShift < MaxShift)
|
|
{
|
|
CurrentShift++;
|
|
RootNode = (!MinShifted) ? &Root.LeftChildOrRootOfLeftList : &Root.RightChildOrRootOfRightList;
|
|
}
|
|
else
|
|
{
|
|
TItem& Item = Allocator.Get(Index);
|
|
if (MinShifted != MaxShifted) // crosses middle
|
|
{
|
|
Item.Next = Root.RootOfOnList;
|
|
Root.RootOfOnList = Index;
|
|
}
|
|
else // we are at the leaf
|
|
{
|
|
if (!MinShifted)
|
|
{
|
|
Item.Next = Root.LeftChildOrRootOfLeftList;
|
|
Root.LeftChildOrRootOfLeftList = Index;
|
|
}
|
|
else
|
|
{
|
|
Item.Next = Root.RightChildOrRootOfRightList;
|
|
Root.RightChildOrRootOfRightList = Index;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename TItem>
|
|
static void AddToIntervalTree(
|
|
TIntervalTreeIndex* RootNode,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
TIntervalTreeIndex Index,
|
|
uint32 StartShift,
|
|
uint32 MaxShift
|
|
)
|
|
{
|
|
GIntervalTreeNodeNodeAllocator.EnsureNoRealloc(1 + MaxShift - StartShift);
|
|
TItem& Item = Allocator.Get(Index);
|
|
check(Item.Next == IntervalTreeInvalidIndex);
|
|
uint64 MinInterval = GetRequestOffset(Item.OffsetAndPakIndex);
|
|
uint64 MaxInterval = MinInterval + Item.Size - 1;
|
|
AddToIntervalTree_Dangerous(RootNode, Allocator, Index, MinInterval, MaxInterval, StartShift, MaxShift);
|
|
|
|
}
|
|
|
|
template<typename TItem>
|
|
static FORCEINLINE bool ScanNodeListForRemoval(
|
|
TIntervalTreeIndex* Iter,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
TIntervalTreeIndex Index,
|
|
uint64 MinInterval,
|
|
uint64 MaxInterval
|
|
)
|
|
{
|
|
while (*Iter != IntervalTreeInvalidIndex)
|
|
{
|
|
|
|
TItem& Item = Allocator.Get(*Iter);
|
|
if (*Iter == Index)
|
|
{
|
|
*Iter = Item.Next;
|
|
Item.Next = IntervalTreeInvalidIndex;
|
|
return true;
|
|
}
|
|
Iter = &Item.Next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template<typename TItem>
|
|
static bool RemoveFromIntervalTree(
|
|
TIntervalTreeIndex* RootNode,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
TIntervalTreeIndex Index,
|
|
uint64 MinInterval,
|
|
uint64 MaxInterval,
|
|
uint32 CurrentShift,
|
|
uint32 MaxShift
|
|
)
|
|
{
|
|
bool bResult = false;
|
|
if (*RootNode != IntervalTreeInvalidIndex)
|
|
{
|
|
int64 MinShifted = HighBit(MinInterval << CurrentShift);
|
|
int64 MaxShifted = HighBit(MaxInterval << CurrentShift);
|
|
FIntervalTreeNode& Root = GIntervalTreeNodeNodeAllocator.Get(*RootNode);
|
|
|
|
if (!MinShifted && !MaxShifted)
|
|
{
|
|
if (CurrentShift == MaxShift)
|
|
{
|
|
bResult = ScanNodeListForRemoval(&Root.LeftChildOrRootOfLeftList, Allocator, Index, MinInterval, MaxInterval);
|
|
}
|
|
else
|
|
{
|
|
bResult = RemoveFromIntervalTree(&Root.LeftChildOrRootOfLeftList, Allocator, Index, MinInterval, MaxInterval, CurrentShift + 1, MaxShift);
|
|
}
|
|
}
|
|
else if (!MinShifted && MaxShifted)
|
|
{
|
|
bResult = ScanNodeListForRemoval(&Root.RootOfOnList, Allocator, Index, MinInterval, MaxInterval);
|
|
}
|
|
else
|
|
{
|
|
if (CurrentShift == MaxShift)
|
|
{
|
|
bResult = ScanNodeListForRemoval(&Root.RightChildOrRootOfRightList, Allocator, Index, MinInterval, MaxInterval);
|
|
}
|
|
else
|
|
{
|
|
bResult = RemoveFromIntervalTree(&Root.RightChildOrRootOfRightList, Allocator, Index, MinInterval, MaxInterval, CurrentShift + 1, MaxShift);
|
|
}
|
|
}
|
|
if (bResult)
|
|
{
|
|
if (Root.LeftChildOrRootOfLeftList == IntervalTreeInvalidIndex && Root.RootOfOnList == IntervalTreeInvalidIndex && Root.RightChildOrRootOfRightList == IntervalTreeInvalidIndex)
|
|
{
|
|
check(&Root == &GIntervalTreeNodeNodeAllocator.Get(*RootNode));
|
|
GIntervalTreeNodeNodeAllocator.Free(*RootNode);
|
|
*RootNode = IntervalTreeInvalidIndex;
|
|
}
|
|
}
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
template<typename TItem>
|
|
static bool RemoveFromIntervalTree(
|
|
TIntervalTreeIndex* RootNode,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
TIntervalTreeIndex Index,
|
|
uint32 StartShift,
|
|
uint32 MaxShift
|
|
)
|
|
{
|
|
TItem& Item = Allocator.Get(Index);
|
|
uint64 MinInterval = GetRequestOffset(Item.OffsetAndPakIndex);
|
|
uint64 MaxInterval = MinInterval + Item.Size - 1;
|
|
return RemoveFromIntervalTree(RootNode, Allocator, Index, MinInterval, MaxInterval, StartShift, MaxShift);
|
|
}
|
|
|
|
template<typename TItem>
|
|
static FORCEINLINE void ScanNodeListForRemovalFunc(
|
|
TIntervalTreeIndex* Iter,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
uint64 MinInterval,
|
|
uint64 MaxInterval,
|
|
TFunctionRef<bool(TIntervalTreeIndex)> Func
|
|
)
|
|
{
|
|
while (*Iter != IntervalTreeInvalidIndex)
|
|
{
|
|
TItem& Item = Allocator.Get(*Iter);
|
|
uint64 Offset = uint64(GetRequestOffset(Item.OffsetAndPakIndex));
|
|
uint64 LastByte = Offset + uint64(Item.Size) - 1;
|
|
|
|
// save the value and then clear it.
|
|
TIntervalTreeIndex NextIndex = Item.Next;
|
|
if (IntervalsIntersect(MinInterval, MaxInterval, Offset, LastByte) && Func(*Iter))
|
|
{
|
|
*Iter = NextIndex; // this may have already be deleted, so cannot rely on the memory block
|
|
}
|
|
else
|
|
{
|
|
Iter = &Item.Next;
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename TItem>
|
|
static void MaybeRemoveOverlappingNodesInIntervalTree(
|
|
TIntervalTreeIndex* RootNode,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
uint64 MinInterval,
|
|
uint64 MaxInterval,
|
|
uint64 MinNode,
|
|
uint64 MaxNode,
|
|
uint32 CurrentShift,
|
|
uint32 MaxShift,
|
|
TFunctionRef<bool(TIntervalTreeIndex)> Func
|
|
)
|
|
{
|
|
if (*RootNode != IntervalTreeInvalidIndex)
|
|
{
|
|
int64 MinShifted = HighBit(MinInterval << CurrentShift);
|
|
int64 MaxShifted = HighBit(MaxInterval << CurrentShift);
|
|
FIntervalTreeNode& Root = GIntervalTreeNodeNodeAllocator.Get(*RootNode);
|
|
uint64 Center = (MinNode + MaxNode + 1) >> 1;
|
|
|
|
//UE_LOG(LogTemp, Warning, TEXT("Exploring Node %X [%d, %d] %d%d interval %llX %llX node interval %llX %llX center %llX "), *RootNode, CurrentShift, MaxShift, !!MinShifted, !!MaxShifted, MinInterval, MaxInterval, MinNode, MaxNode, Center);
|
|
|
|
|
|
if (!MinShifted)
|
|
{
|
|
if (CurrentShift == MaxShift)
|
|
{
|
|
//UE_LOG(LogTemp, Warning, TEXT("LeftBottom %X [%d, %d] %d%d"), *RootNode, CurrentShift, MaxShift, !!MinShifted, !!MaxShifted);
|
|
ScanNodeListForRemovalFunc(&Root.LeftChildOrRootOfLeftList, Allocator, MinInterval, MaxInterval, Func);
|
|
}
|
|
else
|
|
{
|
|
//UE_LOG(LogTemp, Warning, TEXT("LeftRecur %X [%d, %d] %d%d"), *RootNode, CurrentShift, MaxShift, !!MinShifted, !!MaxShifted);
|
|
MaybeRemoveOverlappingNodesInIntervalTree(&Root.LeftChildOrRootOfLeftList, Allocator, MinInterval, FMath::Min(MaxInterval, Center - 1), MinNode, Center - 1, CurrentShift + 1, MaxShift, Func);
|
|
}
|
|
}
|
|
|
|
//UE_LOG(LogTemp, Warning, TEXT("Center %X [%d, %d] %d%d"), *RootNode, CurrentShift, MaxShift, !!MinShifted, !!MaxShifted);
|
|
ScanNodeListForRemovalFunc(&Root.RootOfOnList, Allocator, MinInterval, MaxInterval, Func);
|
|
|
|
if (MaxShifted)
|
|
{
|
|
if (CurrentShift == MaxShift)
|
|
{
|
|
//UE_LOG(LogTemp, Warning, TEXT("RightBottom %X [%d, %d] %d%d"), *RootNode, CurrentShift, MaxShift, !!MinShifted, !!MaxShifted);
|
|
ScanNodeListForRemovalFunc(&Root.RightChildOrRootOfRightList, Allocator, MinInterval, MaxInterval, Func);
|
|
}
|
|
else
|
|
{
|
|
//UE_LOG(LogTemp, Warning, TEXT("RightRecur %X [%d, %d] %d%d"), *RootNode, CurrentShift, MaxShift, !!MinShifted, !!MaxShifted);
|
|
MaybeRemoveOverlappingNodesInIntervalTree(&Root.RightChildOrRootOfRightList, Allocator, FMath::Max(MinInterval, Center), MaxInterval, Center, MaxNode, CurrentShift + 1, MaxShift, Func);
|
|
}
|
|
}
|
|
|
|
//UE_LOG(LogTemp, Warning, TEXT("Done Exploring Node %X [%d, %d] %d%d interval %llX %llX node interval %llX %llX center %llX "), *RootNode, CurrentShift, MaxShift, !!MinShifted, !!MaxShifted, MinInterval, MaxInterval, MinNode, MaxNode, Center);
|
|
|
|
if (Root.LeftChildOrRootOfLeftList == IntervalTreeInvalidIndex && Root.RootOfOnList == IntervalTreeInvalidIndex && Root.RightChildOrRootOfRightList == IntervalTreeInvalidIndex)
|
|
{
|
|
check(&Root == &GIntervalTreeNodeNodeAllocator.Get(*RootNode));
|
|
GIntervalTreeNodeNodeAllocator.Free(*RootNode);
|
|
*RootNode = IntervalTreeInvalidIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
template<typename TItem>
|
|
static FORCEINLINE bool ScanNodeList(
|
|
TIntervalTreeIndex Iter,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
uint64 MinInterval,
|
|
uint64 MaxInterval,
|
|
TFunctionRef<bool(TIntervalTreeIndex)> Func
|
|
)
|
|
{
|
|
while (Iter != IntervalTreeInvalidIndex)
|
|
{
|
|
TItem& Item = Allocator.Get(Iter);
|
|
uint64 Offset = uint64(GetRequestOffset(Item.OffsetAndPakIndex));
|
|
uint64 LastByte = Offset + uint64(Item.Size) - 1;
|
|
if (IntervalsIntersect(MinInterval, MaxInterval, Offset, LastByte))
|
|
{
|
|
if (!Func(Iter))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
Iter = Item.Next;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template<typename TItem>
|
|
static bool OverlappingNodesInIntervalTree(
|
|
TIntervalTreeIndex RootNode,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
uint64 MinInterval,
|
|
uint64 MaxInterval,
|
|
uint64 MinNode,
|
|
uint64 MaxNode,
|
|
uint32 CurrentShift,
|
|
uint32 MaxShift,
|
|
TFunctionRef<bool(TIntervalTreeIndex)> Func
|
|
)
|
|
{
|
|
if (RootNode != IntervalTreeInvalidIndex)
|
|
{
|
|
int64 MinShifted = HighBit(MinInterval << CurrentShift);
|
|
int64 MaxShifted = HighBit(MaxInterval << CurrentShift);
|
|
FIntervalTreeNode& Root = GIntervalTreeNodeNodeAllocator.Get(RootNode);
|
|
uint64 Center = (MinNode + MaxNode + 1) >> 1;
|
|
|
|
if (!MinShifted)
|
|
{
|
|
if (CurrentShift == MaxShift)
|
|
{
|
|
if (!ScanNodeList(Root.LeftChildOrRootOfLeftList, Allocator, MinInterval, MaxInterval, Func))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!OverlappingNodesInIntervalTree(Root.LeftChildOrRootOfLeftList, Allocator, MinInterval, FMath::Min(MaxInterval, Center - 1), MinNode, Center - 1, CurrentShift + 1, MaxShift, Func))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (!ScanNodeList(Root.RootOfOnList, Allocator, MinInterval, MaxInterval, Func))
|
|
{
|
|
return false;
|
|
}
|
|
if (MaxShifted)
|
|
{
|
|
if (CurrentShift == MaxShift)
|
|
{
|
|
if (!ScanNodeList(Root.RightChildOrRootOfRightList, Allocator, MinInterval, MaxInterval, Func))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!OverlappingNodesInIntervalTree(Root.RightChildOrRootOfRightList, Allocator, FMath::Max(MinInterval, Center), MaxInterval, Center, MaxNode, CurrentShift + 1, MaxShift, Func))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template<typename TItem>
|
|
static bool ScanNodeListWithShrinkingInterval(
|
|
TIntervalTreeIndex Iter,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
uint64 MinInterval,
|
|
uint64& MaxInterval,
|
|
TFunctionRef<bool(TIntervalTreeIndex)> Func
|
|
)
|
|
{
|
|
while (Iter != IntervalTreeInvalidIndex)
|
|
{
|
|
TItem& Item = Allocator.Get(Iter);
|
|
uint64 Offset = uint64(GetRequestOffset(Item.OffsetAndPakIndex));
|
|
uint64 LastByte = Offset + uint64(Item.Size) - 1;
|
|
//UE_LOG(LogTemp, Warning, TEXT("Test Overlap %llu %llu %llu %llu"), MinInterval, MaxInterval, Offset, LastByte);
|
|
if (IntervalsIntersect(MinInterval, MaxInterval, Offset, LastByte))
|
|
{
|
|
//UE_LOG(LogTemp, Warning, TEXT("Overlap %llu %llu %llu %llu"), MinInterval, MaxInterval, Offset, LastByte);
|
|
if (!Func(Iter))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
Iter = Item.Next;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template<typename TItem>
|
|
static bool OverlappingNodesInIntervalTreeWithShrinkingInterval(
|
|
TIntervalTreeIndex RootNode,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
uint64 MinInterval,
|
|
uint64& MaxInterval,
|
|
uint64 MinNode,
|
|
uint64 MaxNode,
|
|
uint32 CurrentShift,
|
|
uint32 MaxShift,
|
|
TFunctionRef<bool(TIntervalTreeIndex)> Func
|
|
)
|
|
{
|
|
if (RootNode != IntervalTreeInvalidIndex)
|
|
{
|
|
|
|
int64 MinShifted = HighBit(MinInterval << CurrentShift);
|
|
int64 MaxShifted = HighBit(FMath::Min(MaxInterval, MaxNode) << CurrentShift); // since MaxInterval is changing, we cannot clamp it during recursion.
|
|
FIntervalTreeNode& Root = GIntervalTreeNodeNodeAllocator.Get(RootNode);
|
|
uint64 Center = (MinNode + MaxNode + 1) >> 1;
|
|
|
|
if (!MinShifted)
|
|
{
|
|
if (CurrentShift == MaxShift)
|
|
{
|
|
if (!ScanNodeListWithShrinkingInterval(Root.LeftChildOrRootOfLeftList, Allocator, MinInterval, MaxInterval, Func))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!OverlappingNodesInIntervalTreeWithShrinkingInterval(Root.LeftChildOrRootOfLeftList, Allocator, MinInterval, MaxInterval, MinNode, Center - 1, CurrentShift + 1, MaxShift, Func)) // since MaxInterval is changing, we cannot clamp it during recursion.
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (!ScanNodeListWithShrinkingInterval(Root.RootOfOnList, Allocator, MinInterval, MaxInterval, Func))
|
|
{
|
|
return false;
|
|
}
|
|
MaxShifted = HighBit(FMath::Min(MaxInterval, MaxNode) << CurrentShift); // since MaxInterval is changing, we cannot clamp it during recursion.
|
|
if (MaxShifted)
|
|
{
|
|
if (CurrentShift == MaxShift)
|
|
{
|
|
if (!ScanNodeListWithShrinkingInterval(Root.RightChildOrRootOfRightList, Allocator, MinInterval, MaxInterval, Func))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!OverlappingNodesInIntervalTreeWithShrinkingInterval(Root.RightChildOrRootOfRightList, Allocator, FMath::Max(MinInterval, Center), MaxInterval, Center, MaxNode, CurrentShift + 1, MaxShift, Func))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
template<typename TItem>
|
|
static void MaskInterval(
|
|
TIntervalTreeIndex Index,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
uint64 MinInterval,
|
|
uint64 MaxInterval,
|
|
uint32 BytesToBitsShift,
|
|
uint64* Bits
|
|
)
|
|
{
|
|
TItem& Item = Allocator.Get(Index);
|
|
uint64 Offset = uint64(GetRequestOffset(Item.OffsetAndPakIndex));
|
|
uint64 LastByte = Offset + uint64(Item.Size) - 1;
|
|
uint64 InterMinInterval = FMath::Max(MinInterval, Offset);
|
|
uint64 InterMaxInterval = FMath::Min(MaxInterval, LastByte);
|
|
if (InterMinInterval <= InterMaxInterval)
|
|
{
|
|
uint32 FirstBit = uint32((InterMinInterval - MinInterval) >> BytesToBitsShift);
|
|
uint32 LastBit = uint32((InterMaxInterval - MinInterval) >> BytesToBitsShift);
|
|
uint32 FirstQWord = FirstBit >> 6;
|
|
uint32 LastQWord = LastBit >> 6;
|
|
uint32 FirstBitQWord = FirstBit & 63;
|
|
uint32 LastBitQWord = LastBit & 63;
|
|
if (FirstQWord == LastQWord)
|
|
{
|
|
Bits[FirstQWord] |= ((MAX_uint64 << FirstBitQWord) & (MAX_uint64 >> (63 - LastBitQWord)));
|
|
}
|
|
else
|
|
{
|
|
Bits[FirstQWord] |= (MAX_uint64 << FirstBitQWord);
|
|
for (uint32 QWordIndex = FirstQWord + 1; QWordIndex < LastQWord; QWordIndex++)
|
|
{
|
|
Bits[QWordIndex] = MAX_uint64;
|
|
}
|
|
Bits[LastQWord] |= (MAX_uint64 >> (63 - LastBitQWord));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
template<typename TItem>
|
|
static void OverlappingNodesInIntervalTreeMask(
|
|
TIntervalTreeIndex RootNode,
|
|
TIntervalTreeAllocator<TItem>& Allocator,
|
|
uint64 MinInterval,
|
|
uint64 MaxInterval,
|
|
uint64 MinNode,
|
|
uint64 MaxNode,
|
|
uint32 CurrentShift,
|
|
uint32 MaxShift,
|
|
uint32 BytesToBitsShift,
|
|
uint64* Bits
|
|
)
|
|
{
|
|
OverlappingNodesInIntervalTree(
|
|
RootNode,
|
|
Allocator,
|
|
MinInterval,
|
|
MaxInterval,
|
|
MinNode,
|
|
MaxNode,
|
|
CurrentShift,
|
|
MaxShift,
|
|
[&Allocator, MinInterval, MaxInterval, BytesToBitsShift, Bits](TIntervalTreeIndex Index) -> bool
|
|
{
|
|
MaskInterval(Index, Allocator, MinInterval, MaxInterval, BytesToBitsShift, Bits);
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
|
|
|
|
|
|
class IPakRequestor
|
|
{
|
|
friend class FPakPrecacher;
|
|
FJoinedOffsetAndPakIndex OffsetAndPakIndex; // this is used for searching and filled in when you make the request
|
|
uint64 UniqueID;
|
|
TIntervalTreeIndex InRequestIndex;
|
|
public:
|
|
IPakRequestor()
|
|
: OffsetAndPakIndex(MAX_uint64) // invalid value
|
|
, UniqueID(0)
|
|
, InRequestIndex(IntervalTreeInvalidIndex)
|
|
{
|
|
}
|
|
virtual ~IPakRequestor()
|
|
{
|
|
}
|
|
virtual void RequestIsComplete()
|
|
{
|
|
}
|
|
};
|
|
|
|
static FPakPrecacher* PakPrecacherSingleton = nullptr;
|
|
|
|
class FPakPrecacher
|
|
{
|
|
enum class EInRequestStatus
|
|
{
|
|
Complete,
|
|
Waiting,
|
|
InFlight,
|
|
Num
|
|
};
|
|
|
|
enum class EBlockStatus
|
|
{
|
|
InFlight,
|
|
Complete,
|
|
Num
|
|
};
|
|
|
|
IPlatformFile* LowerLevel;
|
|
FCriticalSection CachedFilesScopeLock;
|
|
FJoinedOffsetAndPakIndex LastReadRequest;
|
|
uint64 NextUniqueID;
|
|
int64 BlockMemory;
|
|
int64 BlockMemoryHighWater;
|
|
|
|
struct FCacheBlock
|
|
{
|
|
FJoinedOffsetAndPakIndex OffsetAndPakIndex;
|
|
int64 Size;
|
|
uint8 *Memory;
|
|
uint32 InRequestRefCount;
|
|
TIntervalTreeIndex Index;
|
|
TIntervalTreeIndex Next;
|
|
EBlockStatus Status;
|
|
|
|
FCacheBlock()
|
|
: OffsetAndPakIndex(0)
|
|
, Size(0)
|
|
, Memory(nullptr)
|
|
, InRequestRefCount(0)
|
|
, Index(IntervalTreeInvalidIndex)
|
|
, Next(IntervalTreeInvalidIndex)
|
|
, Status(EBlockStatus::InFlight)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct FPakInRequest
|
|
{
|
|
FJoinedOffsetAndPakIndex OffsetAndPakIndex;
|
|
int64 Size;
|
|
IPakRequestor* Owner;
|
|
uint64 UniqueID;
|
|
TIntervalTreeIndex Index;
|
|
TIntervalTreeIndex Next;
|
|
EAsyncIOPriority Priority;
|
|
EInRequestStatus Status;
|
|
|
|
FPakInRequest()
|
|
: OffsetAndPakIndex(0)
|
|
, Size(0)
|
|
, Owner(nullptr)
|
|
, UniqueID(0)
|
|
, Index(IntervalTreeInvalidIndex)
|
|
, Next(IntervalTreeInvalidIndex)
|
|
, Priority(AIOP_MIN)
|
|
, Status(EInRequestStatus::Waiting)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct FPakData
|
|
{
|
|
IAsyncReadFileHandle* Handle;
|
|
int64 TotalSize;
|
|
uint64 MaxNode;
|
|
uint32 StartShift;
|
|
uint32 MaxShift;
|
|
uint32 BytesToBitsShift;
|
|
FName Name;
|
|
|
|
TIntervalTreeIndex InRequests[AIOP_NUM][(int32)EInRequestStatus::Num];
|
|
TIntervalTreeIndex CacheBlocks[(int32)EBlockStatus::Num];
|
|
|
|
FPakData(IAsyncReadFileHandle* InHandle, FName InName, int64 InTotalSize)
|
|
: Handle(InHandle)
|
|
, TotalSize(InTotalSize)
|
|
, StartShift(0)
|
|
, MaxShift(0)
|
|
, BytesToBitsShift(0)
|
|
, Name(InName)
|
|
{
|
|
check(Handle && TotalSize > 0 && Name != NAME_None);
|
|
for (int32 Index = 0; Index < AIOP_NUM; Index++)
|
|
{
|
|
for (int32 IndexInner = 0; IndexInner < (int32)EInRequestStatus::Num; IndexInner++)
|
|
{
|
|
InRequests[Index][IndexInner] = IntervalTreeInvalidIndex;
|
|
}
|
|
}
|
|
for (int32 IndexInner = 0; IndexInner < (int32)EBlockStatus::Num; IndexInner++)
|
|
{
|
|
CacheBlocks[IndexInner] = IntervalTreeInvalidIndex;
|
|
}
|
|
uint64 StartingLastByte = FMath::Max((uint64)TotalSize, (uint64)PAK_CACHE_GRANULARITY);
|
|
StartingLastByte--;
|
|
|
|
{
|
|
uint64 LastByte = StartingLastByte;
|
|
while (!HighBit(LastByte))
|
|
{
|
|
LastByte <<= 1;
|
|
StartShift++;
|
|
}
|
|
}
|
|
{
|
|
uint64 LastByte = StartingLastByte;
|
|
uint64 Block = (uint64)PAK_CACHE_GRANULARITY;
|
|
|
|
while (Block)
|
|
{
|
|
Block >>= 1;
|
|
LastByte >>= 1;
|
|
BytesToBitsShift++;
|
|
}
|
|
BytesToBitsShift--;
|
|
check(1 << BytesToBitsShift == PAK_CACHE_GRANULARITY);
|
|
MaxShift = StartShift;
|
|
while (LastByte)
|
|
{
|
|
LastByte >>= 1;
|
|
MaxShift++;
|
|
}
|
|
MaxNode = MAX_uint64 >> StartShift;
|
|
check(MaxNode >= StartingLastByte && (MaxNode >> 1) < StartingLastByte);
|
|
//UE_LOG(LogTemp, Warning, TEXT("Test %d %llX %llX "), MaxShift, (uint64(PAK_CACHE_GRANULARITY) << (MaxShift + 1)), (uint64(PAK_CACHE_GRANULARITY) << MaxShift));
|
|
check(MaxShift && (uint64(PAK_CACHE_GRANULARITY) << (MaxShift + 1)) == 0 && (uint64(PAK_CACHE_GRANULARITY) << MaxShift) != 0);
|
|
}
|
|
}
|
|
};
|
|
TMap<FName, uint16> CachedPaks;
|
|
TArray<FPakData> CachedPakData;
|
|
|
|
TIntervalTreeAllocator<FPakInRequest> InRequestAllocator;
|
|
TIntervalTreeAllocator<FCacheBlock> CacheBlockAllocator;
|
|
TMap<uint64, TIntervalTreeIndex> OutstandingRequests;
|
|
|
|
|
|
|
|
struct FRequestToLower
|
|
{
|
|
IAsyncReadRequest* RequestHandle;
|
|
TIntervalTreeIndex BlockIndex;
|
|
FRequestToLower()
|
|
: RequestHandle(nullptr)
|
|
, BlockIndex(IntervalTreeInvalidIndex)
|
|
{
|
|
}
|
|
};
|
|
|
|
FRequestToLower RequestsToLower[PAK_CACHE_MAX_REQUESTS];
|
|
TArray<IAsyncReadRequest*> RequestsToDelete;
|
|
int32 NotifyRecursion;
|
|
FAsyncFileCallBack CallbackFromLower;
|
|
|
|
uint32 Loads;
|
|
uint32 Frees;
|
|
uint64 LoadSize;
|
|
|
|
public:
|
|
|
|
static void Init(IPlatformFile* InLowerLevel)
|
|
{
|
|
if (!PakPrecacherSingleton)
|
|
{
|
|
verify(!FPlatformAtomics::InterlockedCompareExchangePointer((void**)&PakPrecacherSingleton, new FPakPrecacher(InLowerLevel), NULL));
|
|
}
|
|
check(PakPrecacherSingleton);
|
|
}
|
|
|
|
static FPakPrecacher& Get()
|
|
{
|
|
check(PakPrecacherSingleton);
|
|
return *PakPrecacherSingleton;
|
|
}
|
|
|
|
FPakPrecacher(IPlatformFile* InLowerLevel)
|
|
: LowerLevel(InLowerLevel)
|
|
, LastReadRequest(0)
|
|
, NextUniqueID(1)
|
|
, BlockMemory(0)
|
|
, BlockMemoryHighWater(0)
|
|
, NotifyRecursion(0)
|
|
, Loads(0)
|
|
, Frees(0)
|
|
, LoadSize(0)
|
|
{
|
|
check(LowerLevel);
|
|
if (FPlatformProcess::SupportsMultithreading())
|
|
{
|
|
GPakCache_MaxRequestsToLowerLevel = FMath::Max(FMath::Min(FPlatformMisc::NumberOfIOWorkerThreadsToSpawn(), GPakCache_MaxRequestsToLowerLevel), 1);
|
|
}
|
|
else
|
|
{
|
|
GPakCache_MaxRequestsToLowerLevel = 0;
|
|
}
|
|
check(GPakCache_MaxRequestsToLowerLevel <= PAK_CACHE_MAX_REQUESTS);
|
|
CallbackFromLower =
|
|
[this](bool bWasCanceled, IAsyncReadRequest* Request)
|
|
{
|
|
NewRequestsToLowerComplete(bWasCanceled, Request);
|
|
};
|
|
}
|
|
|
|
IPlatformFile* GetLowerLevelHandle()
|
|
{
|
|
check(LowerLevel);
|
|
return LowerLevel;
|
|
}
|
|
|
|
int64 MaxMemPrecache()
|
|
{
|
|
return GPakCache_MaxPrecacheMemMB * 1024ll * 1024ll;
|
|
}
|
|
|
|
bool HasEnoughRoomForPrecache()
|
|
{
|
|
return GPakCache_MaxPrecacheMemMB == 0 || BlockMemory < MaxMemPrecache();
|
|
}
|
|
|
|
private: // below here we assume CachedFilesScopeLock until we get to the next section
|
|
|
|
uint16 GetRequestPakIndex(FJoinedOffsetAndPakIndex OffsetAndPakIndex)
|
|
{
|
|
uint16 Result = GetRequestPakIndexLow(OffsetAndPakIndex);
|
|
check(Result < CachedPakData.Num());
|
|
return Result;
|
|
}
|
|
|
|
FJoinedOffsetAndPakIndex FirstUnfilledBlockForRequest(TIntervalTreeIndex NewIndex, FJoinedOffsetAndPakIndex ReadHead = 0)
|
|
{
|
|
// CachedFilesScopeLock is locked
|
|
FPakInRequest& Request = InRequestAllocator.Get(NewIndex);
|
|
uint16 PakIndex = GetRequestPakIndex(Request.OffsetAndPakIndex);
|
|
int64 Offset = GetRequestOffset(Request.OffsetAndPakIndex);
|
|
int64 Size = Request.Size;
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
check(Offset + Request.Size <= Pak.TotalSize && Size > 0 && Request.Priority >= AIOP_MIN && Request.Priority <= AIOP_MAX && Request.Status != EInRequestStatus::Complete && Request.Owner);
|
|
if (PakIndex != GetRequestPakIndex(ReadHead))
|
|
{
|
|
// this is in a different pak, so we ignore the read head position
|
|
ReadHead = 0;
|
|
}
|
|
if (ReadHead)
|
|
{
|
|
// trim to the right of the read head
|
|
int64 Trim = FMath::Max(Offset, GetRequestOffset(ReadHead)) - Offset;
|
|
Offset += Trim;
|
|
Size -= Trim;
|
|
}
|
|
|
|
static TArray<uint64> InFlightOrDone;
|
|
|
|
int64 FirstByte = AlignDown(Offset, PAK_CACHE_GRANULARITY);
|
|
int64 LastByte = Align(Offset + Size, PAK_CACHE_GRANULARITY) - 1;
|
|
uint32 NumBits = (PAK_CACHE_GRANULARITY + LastByte - FirstByte) / PAK_CACHE_GRANULARITY;
|
|
uint32 NumQWords = (NumBits + 63) >> 6;
|
|
InFlightOrDone.Reset();
|
|
InFlightOrDone.AddZeroed(NumQWords);
|
|
if (NumBits != NumQWords * 64)
|
|
{
|
|
uint32 Extras = NumQWords * 64 - NumBits;
|
|
InFlightOrDone[NumQWords - 1] = (MAX_uint64 << (64 - Extras));
|
|
}
|
|
|
|
if (Pak.CacheBlocks[(int32)EBlockStatus::Complete] != IntervalTreeInvalidIndex)
|
|
{
|
|
OverlappingNodesInIntervalTreeMask<FCacheBlock>(
|
|
Pak.CacheBlocks[(int32)EBlockStatus::Complete],
|
|
CacheBlockAllocator,
|
|
FirstByte,
|
|
LastByte,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
Pak.BytesToBitsShift,
|
|
&InFlightOrDone[0]
|
|
);
|
|
}
|
|
if (Request.Status == EInRequestStatus::Waiting && Pak.CacheBlocks[(int32)EBlockStatus::InFlight] != IntervalTreeInvalidIndex)
|
|
{
|
|
OverlappingNodesInIntervalTreeMask<FCacheBlock>(
|
|
Pak.CacheBlocks[(int32)EBlockStatus::InFlight],
|
|
CacheBlockAllocator,
|
|
FirstByte,
|
|
LastByte,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
Pak.BytesToBitsShift,
|
|
&InFlightOrDone[0]
|
|
);
|
|
}
|
|
for (int32 Index = 0; Index < NumQWords; Index++)
|
|
{
|
|
if (InFlightOrDone[Index] != MAX_uint64)
|
|
{
|
|
uint64 Mask = InFlightOrDone[Index];
|
|
int64 FinalOffset = FirstByte + PAK_CACHE_GRANULARITY * 64 * Index;
|
|
while (Mask & 1)
|
|
{
|
|
FinalOffset += PAK_CACHE_GRANULARITY;
|
|
Mask >>= 1;
|
|
}
|
|
return MakeJoinedRequest(PakIndex, FinalOffset);
|
|
}
|
|
}
|
|
return MAX_uint64;
|
|
}
|
|
|
|
bool AddRequest(TIntervalTreeIndex NewIndex)
|
|
{
|
|
// CachedFilesScopeLock is locked
|
|
FPakInRequest& Request = InRequestAllocator.Get(NewIndex);
|
|
uint16 PakIndex = GetRequestPakIndex(Request.OffsetAndPakIndex);
|
|
int64 Offset = GetRequestOffset(Request.OffsetAndPakIndex);
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
check(Offset + Request.Size <= Pak.TotalSize && Request.Size > 0 && Request.Priority >= AIOP_MIN && Request.Priority <= AIOP_MAX && Request.Status == EInRequestStatus::Waiting && Request.Owner);
|
|
|
|
static TArray<uint64> InFlightOrDone;
|
|
|
|
int64 FirstByte = AlignDown(Offset, PAK_CACHE_GRANULARITY);
|
|
int64 LastByte = Align(Offset + Request.Size, PAK_CACHE_GRANULARITY) - 1;
|
|
uint32 NumBits = (PAK_CACHE_GRANULARITY + LastByte - FirstByte) / PAK_CACHE_GRANULARITY;
|
|
uint32 NumQWords = (NumBits + 63) >> 6;
|
|
InFlightOrDone.Reset();
|
|
InFlightOrDone.AddZeroed(NumQWords);
|
|
if (NumBits != NumQWords * 64)
|
|
{
|
|
uint32 Extras = NumQWords * 64 - NumBits;
|
|
InFlightOrDone[NumQWords - 1] = (MAX_uint64 << (64 - Extras));
|
|
}
|
|
|
|
if (Pak.CacheBlocks[(int32)EBlockStatus::Complete] != IntervalTreeInvalidIndex)
|
|
{
|
|
Request.Status = EInRequestStatus::Complete;
|
|
OverlappingNodesInIntervalTree<FCacheBlock>(
|
|
Pak.CacheBlocks[(int32)EBlockStatus::Complete],
|
|
CacheBlockAllocator,
|
|
FirstByte,
|
|
LastByte,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this, &Pak, FirstByte, LastByte](TIntervalTreeIndex Index) -> bool
|
|
{
|
|
CacheBlockAllocator.Get(Index).InRequestRefCount++;
|
|
MaskInterval(Index, CacheBlockAllocator, FirstByte, LastByte, Pak.BytesToBitsShift, &InFlightOrDone[0]);
|
|
return true;
|
|
}
|
|
);
|
|
for (int32 Index = 0; Index < NumQWords; Index++)
|
|
{
|
|
if (InFlightOrDone[Index] != MAX_uint64)
|
|
{
|
|
Request.Status = EInRequestStatus::Waiting;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Request.Status == EInRequestStatus::Waiting)
|
|
{
|
|
if (Pak.CacheBlocks[(int32)EBlockStatus::InFlight] != IntervalTreeInvalidIndex)
|
|
{
|
|
Request.Status = EInRequestStatus::InFlight;
|
|
OverlappingNodesInIntervalTree<FCacheBlock>(
|
|
Pak.CacheBlocks[(int32)EBlockStatus::InFlight],
|
|
CacheBlockAllocator,
|
|
FirstByte,
|
|
LastByte,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this, &Pak, FirstByte, LastByte](TIntervalTreeIndex Index) -> bool
|
|
{
|
|
CacheBlockAllocator.Get(Index).InRequestRefCount++;
|
|
MaskInterval(Index, CacheBlockAllocator, FirstByte, LastByte, Pak.BytesToBitsShift, &InFlightOrDone[0]);
|
|
return true;
|
|
}
|
|
);
|
|
|
|
for (int32 Index = 0; Index < NumQWords; Index++)
|
|
{
|
|
if (InFlightOrDone[Index] != MAX_uint64)
|
|
{
|
|
Request.Status = EInRequestStatus::Waiting;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if DO_CHECK
|
|
OverlappingNodesInIntervalTree<FCacheBlock>(
|
|
Pak.CacheBlocks[(int32)EBlockStatus::InFlight],
|
|
CacheBlockAllocator,
|
|
FirstByte,
|
|
LastByte,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this, &Pak, FirstByte, LastByte](TIntervalTreeIndex Index) -> bool
|
|
{
|
|
check(0); // if we are complete, then how come there are overlapping in flight blocks?
|
|
return true;
|
|
}
|
|
);
|
|
#endif
|
|
}
|
|
{
|
|
AddToIntervalTree<FPakInRequest>(
|
|
&Pak.InRequests[Request.Priority][(int32)Request.Status],
|
|
InRequestAllocator,
|
|
NewIndex,
|
|
Pak.StartShift,
|
|
Pak.MaxShift
|
|
);
|
|
}
|
|
check(&Request == &InRequestAllocator.Get(NewIndex));
|
|
if (Request.Status == EInRequestStatus::Complete)
|
|
{
|
|
NotifyComplete(NewIndex);
|
|
return true;
|
|
}
|
|
else if (Request.Status == EInRequestStatus::Waiting)
|
|
{
|
|
StartNextRequest();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ClearBlock(FCacheBlock &Block)
|
|
{
|
|
#if 0
|
|
if (GetRequestPakIndex(Block.OffsetAndPakIndex) == 1 && GetRequestOffset(Block.OffsetAndPakIndex) <= 0x22f573d3ll && GetRequestOffset(Block.OffsetAndPakIndex) + Block.Size >= 0x22f573d3ll)
|
|
{
|
|
UE_LOG(LogTemp, Error, TEXT("Hi"));
|
|
}
|
|
#endif
|
|
if (Block.Memory)
|
|
{
|
|
BlockMemory -= Block.Size;
|
|
DEC_MEMORY_STAT_BY(STAT_PakCacheMem, Block.Size);
|
|
FMemory::Free(Block.Memory);
|
|
Block.Memory = nullptr;
|
|
}
|
|
Block.Next = IntervalTreeInvalidIndex;
|
|
CacheBlockAllocator.Free(Block.Index);
|
|
}
|
|
|
|
void ClearRequest(FPakInRequest& DoneRequest)
|
|
{
|
|
uint64 Id = DoneRequest.UniqueID;
|
|
TIntervalTreeIndex Index = DoneRequest.Index;
|
|
|
|
DoneRequest.OffsetAndPakIndex = 0;
|
|
DoneRequest.Size = 0;
|
|
DoneRequest.Owner = nullptr;
|
|
DoneRequest.UniqueID = 0;
|
|
DoneRequest.Index = IntervalTreeInvalidIndex;
|
|
DoneRequest.Next = IntervalTreeInvalidIndex;
|
|
DoneRequest.Priority = AIOP_MIN;
|
|
DoneRequest.Status = EInRequestStatus::Num;
|
|
|
|
verify(OutstandingRequests.Remove(Id) == 1);
|
|
InRequestAllocator.Free(Index);
|
|
}
|
|
|
|
void RemoveRequest(TIntervalTreeIndex Index)
|
|
{
|
|
// CachedFilesScopeLock is locked
|
|
FPakInRequest& Request = InRequestAllocator.Get(Index);
|
|
uint16 PakIndex = GetRequestPakIndex(Request.OffsetAndPakIndex);
|
|
int64 Offset = GetRequestOffset(Request.OffsetAndPakIndex);
|
|
int64 Size = Request.Size;
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
check(Offset + Request.Size <= Pak.TotalSize && Request.Size > 0 && Request.Priority >= AIOP_MIN && Request.Priority <= AIOP_MAX && int32(Request.Status) >= 0 && int32(Request.Status) < int32(EInRequestStatus::Num));
|
|
|
|
if (RemoveFromIntervalTree<FPakInRequest>(&Pak.InRequests[Request.Priority][(int32)Request.Status], InRequestAllocator, Index, Pak.StartShift, Pak.MaxShift))
|
|
{
|
|
MaybeRemoveOverlappingNodesInIntervalTree<FCacheBlock>(
|
|
&Pak.CacheBlocks[(int32)EBlockStatus::Complete],
|
|
CacheBlockAllocator,
|
|
Offset,
|
|
Offset + Size - 1,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this](TIntervalTreeIndex BlockIndex) -> bool
|
|
{
|
|
FCacheBlock &Block = CacheBlockAllocator.Get(BlockIndex);
|
|
check(Block.InRequestRefCount);
|
|
if (!--Block.InRequestRefCount)
|
|
{
|
|
ClearBlock(Block);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
);
|
|
|
|
OverlappingNodesInIntervalTree<FCacheBlock>(
|
|
Pak.CacheBlocks[(int32)EBlockStatus::InFlight],
|
|
CacheBlockAllocator,
|
|
Offset,
|
|
Offset + Size - 1,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this](TIntervalTreeIndex BlockIndex) -> bool
|
|
{
|
|
FCacheBlock &Block = CacheBlockAllocator.Get(BlockIndex);
|
|
check(Block.InRequestRefCount);
|
|
Block.InRequestRefCount--;
|
|
return true;
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
check(0); // not found
|
|
}
|
|
ClearRequest(Request);
|
|
}
|
|
|
|
void NotifyComplete(TIntervalTreeIndex RequestIndex)
|
|
{
|
|
// CachedFilesScopeLock is locked
|
|
FPakInRequest& Request = InRequestAllocator.Get(RequestIndex);
|
|
|
|
uint16 PakIndex = GetRequestPakIndex(Request.OffsetAndPakIndex);
|
|
int64 Offset = GetRequestOffset(Request.OffsetAndPakIndex);
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
check(Offset + Request.Size <= Pak.TotalSize && Request.Size > 0 && Request.Priority >= AIOP_MIN && Request.Priority <= AIOP_MAX && Request.Status == EInRequestStatus::Complete);
|
|
|
|
check(Request.Owner && Request.UniqueID);
|
|
|
|
if (Request.Status == EInRequestStatus::Complete && Request.UniqueID == Request.Owner->UniqueID && RequestIndex == Request.Owner->InRequestIndex && Request.OffsetAndPakIndex == Request.Owner->OffsetAndPakIndex)
|
|
{
|
|
UE_LOG(LogPakFile, Verbose, TEXT("FPakReadRequest[%016llX, %016llX) Notify complete"), Request.OffsetAndPakIndex, Request.OffsetAndPakIndex + Request.Size);
|
|
Request.Owner->RequestIsComplete();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
check(0); // request should have been found
|
|
}
|
|
}
|
|
|
|
FJoinedOffsetAndPakIndex GetNextBlock(EAsyncIOPriority& OutPriority)
|
|
{
|
|
bool bAcceptingPrecacheRequests = HasEnoughRoomForPrecache();
|
|
|
|
// CachedFilesScopeLock is locked
|
|
uint16 BestPakIndex = 0;
|
|
FJoinedOffsetAndPakIndex BestNext = MAX_uint64;
|
|
|
|
OutPriority = AIOP_MIN;
|
|
for (EAsyncIOPriority Priority = AIOP_MAX;; Priority = EAsyncIOPriority(int32(Priority) - 1))
|
|
{
|
|
if (Priority == AIOP_Precache && !bAcceptingPrecacheRequests)
|
|
{
|
|
break;
|
|
}
|
|
for (int32 Pass = 0; ; Pass++)
|
|
{
|
|
FJoinedOffsetAndPakIndex LocalLastReadRequest = Pass ? 0 : LastReadRequest;
|
|
|
|
uint16 PakIndex = GetRequestPakIndex(LocalLastReadRequest);
|
|
int64 Offset = GetRequestOffset(LocalLastReadRequest);
|
|
check(Offset <= CachedPakData[PakIndex].TotalSize);
|
|
|
|
|
|
for (; BestNext == MAX_uint64 && PakIndex < CachedPakData.Num(); PakIndex++)
|
|
{
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
if (Pak.InRequests[Priority][(int32)EInRequestStatus::Waiting] != IntervalTreeInvalidIndex)
|
|
{
|
|
uint64 Limit = uint64(Pak.TotalSize - 1);
|
|
if (BestNext != MAX_uint64 && GetRequestPakIndex(BestNext) == PakIndex)
|
|
{
|
|
Limit = GetRequestOffset(BestNext) - 1;
|
|
}
|
|
|
|
OverlappingNodesInIntervalTreeWithShrinkingInterval<FPakInRequest>(
|
|
Pak.InRequests[Priority][(int32)EInRequestStatus::Waiting],
|
|
InRequestAllocator,
|
|
uint64(Offset),
|
|
Limit,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this, &Pak, &BestNext, &BestPakIndex, PakIndex, &Limit, LocalLastReadRequest](TIntervalTreeIndex Index) -> bool
|
|
{
|
|
FJoinedOffsetAndPakIndex First = FirstUnfilledBlockForRequest(Index, LocalLastReadRequest);
|
|
check(LocalLastReadRequest != 0 || First != MAX_uint64); // if there was not trimming, and this thing is in the waiting list, then why was no start block found?
|
|
if (First < BestNext)
|
|
{
|
|
BestNext = First;
|
|
BestPakIndex = PakIndex;
|
|
Limit = GetRequestOffset(BestNext) - 1;
|
|
}
|
|
return true; // always have to keep going because we want the smallest one
|
|
}
|
|
);
|
|
}
|
|
}
|
|
if (!LocalLastReadRequest)
|
|
{
|
|
break; // this was a full pass
|
|
}
|
|
}
|
|
|
|
if (Priority == AIOP_MIN || BestNext != MAX_uint64)
|
|
{
|
|
OutPriority = Priority;
|
|
break;
|
|
}
|
|
}
|
|
return BestNext;
|
|
}
|
|
|
|
bool AddNewBlock()
|
|
{
|
|
// CachedFilesScopeLock is locked
|
|
EAsyncIOPriority RequestPriority;
|
|
FJoinedOffsetAndPakIndex BestNext = GetNextBlock(RequestPriority);
|
|
if (BestNext == MAX_uint64)
|
|
{
|
|
return false;
|
|
}
|
|
uint16 PakIndex = GetRequestPakIndex(BestNext);
|
|
int64 Offset = GetRequestOffset(BestNext);
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
check(Offset < Pak.TotalSize);
|
|
int64 FirstByte = AlignDown(Offset, PAK_CACHE_GRANULARITY);
|
|
int64 LastByte = FMath::Min(Align(FirstByte + PAK_CACHE_MAX_REQUEST, PAK_CACHE_GRANULARITY) - 1, Pak.TotalSize - 1);
|
|
check(FirstByte >= 0 && LastByte < Pak.TotalSize && LastByte >= 0 && LastByte >= FirstByte);
|
|
|
|
uint32 NumBits = (PAK_CACHE_GRANULARITY + LastByte - FirstByte) / PAK_CACHE_GRANULARITY;
|
|
uint32 NumQWords = (NumBits + 63) >> 6;
|
|
|
|
static TArray<uint64> InFlightOrDone;
|
|
InFlightOrDone.Reset();
|
|
InFlightOrDone.AddZeroed(NumQWords);
|
|
if (NumBits != NumQWords * 64)
|
|
{
|
|
uint32 Extras = NumQWords * 64 - NumBits;
|
|
InFlightOrDone[NumQWords - 1] = (MAX_uint64 << (64 - Extras));
|
|
}
|
|
|
|
if (Pak.CacheBlocks[(int32)EBlockStatus::Complete] != IntervalTreeInvalidIndex)
|
|
{
|
|
OverlappingNodesInIntervalTreeMask<FCacheBlock>(
|
|
Pak.CacheBlocks[(int32)EBlockStatus::Complete],
|
|
CacheBlockAllocator,
|
|
FirstByte,
|
|
LastByte,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
Pak.BytesToBitsShift,
|
|
&InFlightOrDone[0]
|
|
);
|
|
}
|
|
if (Pak.CacheBlocks[(int32)EBlockStatus::InFlight] != IntervalTreeInvalidIndex)
|
|
{
|
|
OverlappingNodesInIntervalTreeMask<FCacheBlock>(
|
|
Pak.CacheBlocks[(int32)EBlockStatus::InFlight],
|
|
CacheBlockAllocator,
|
|
FirstByte,
|
|
LastByte,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
Pak.BytesToBitsShift,
|
|
&InFlightOrDone[0]
|
|
);
|
|
}
|
|
|
|
static TArray<uint64> Requested;
|
|
Requested.Reset();
|
|
Requested.AddZeroed(NumQWords);
|
|
for (EAsyncIOPriority Priority = AIOP_MAX;; Priority = EAsyncIOPriority(int32(Priority) - 1))
|
|
{
|
|
if (Priority + PAK_CACHE_MAX_PRIORITY_DIFFERENCE_MERGE < RequestPriority)
|
|
{
|
|
break;
|
|
}
|
|
if (Pak.InRequests[Priority][(int32)EInRequestStatus::Waiting] != IntervalTreeInvalidIndex)
|
|
{
|
|
OverlappingNodesInIntervalTreeMask<FPakInRequest>(
|
|
Pak.InRequests[Priority][(int32)EInRequestStatus::Waiting],
|
|
InRequestAllocator,
|
|
FirstByte,
|
|
LastByte,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
Pak.BytesToBitsShift,
|
|
&Requested[0]
|
|
);
|
|
}
|
|
if (Priority == AIOP_MIN)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
int64 Size = PAK_CACHE_GRANULARITY * 64 * NumQWords;
|
|
for (int32 Index = 0; Index < NumQWords; Index++)
|
|
{
|
|
uint64 NotAlreadyInFlightAndRequested = ((~InFlightOrDone[Index]) & Requested[Index]);
|
|
if (NotAlreadyInFlightAndRequested != MAX_uint64)
|
|
{
|
|
Size = PAK_CACHE_GRANULARITY * 64 * Index;
|
|
while (NotAlreadyInFlightAndRequested & 1)
|
|
{
|
|
Size += PAK_CACHE_GRANULARITY;
|
|
NotAlreadyInFlightAndRequested >>= 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
check(Size > 0 && Size <= PAK_CACHE_MAX_REQUEST);
|
|
Size = FMath::Min(FirstByte + Size, LastByte + 1) - FirstByte;
|
|
|
|
TIntervalTreeIndex NewIndex = CacheBlockAllocator.Alloc();
|
|
|
|
FCacheBlock& Block = CacheBlockAllocator.Get(NewIndex);
|
|
Block.Index = NewIndex;
|
|
Block.InRequestRefCount = 0;
|
|
Block.Memory = nullptr;
|
|
Block.OffsetAndPakIndex = MakeJoinedRequest(PakIndex, FirstByte);
|
|
Block.Size = Size;
|
|
Block.Status = EBlockStatus::InFlight;
|
|
|
|
AddToIntervalTree<FCacheBlock>(
|
|
&Pak.CacheBlocks[(int32)EBlockStatus::InFlight],
|
|
CacheBlockAllocator,
|
|
NewIndex,
|
|
Pak.StartShift,
|
|
Pak.MaxShift
|
|
);
|
|
|
|
TArray<TIntervalTreeIndex> Inflights;
|
|
|
|
for (EAsyncIOPriority Priority = AIOP_MAX;; Priority = EAsyncIOPriority(int32(Priority) - 1))
|
|
{
|
|
if (Pak.InRequests[Priority][(int32)EInRequestStatus::Waiting] != IntervalTreeInvalidIndex)
|
|
{
|
|
MaybeRemoveOverlappingNodesInIntervalTree<FPakInRequest>(
|
|
&Pak.InRequests[Priority][(int32)EInRequestStatus::Waiting],
|
|
InRequestAllocator,
|
|
uint64(FirstByte),
|
|
uint64(FirstByte + Size - 1),
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this, &Block, &Inflights](TIntervalTreeIndex RequestIndex) -> bool
|
|
{
|
|
Block.InRequestRefCount++;
|
|
if (FirstUnfilledBlockForRequest(RequestIndex) == MAX_uint64)
|
|
{
|
|
InRequestAllocator.Get(RequestIndex).Next = IntervalTreeInvalidIndex;
|
|
Inflights.Add(RequestIndex);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
);
|
|
}
|
|
#if DO_CHECK
|
|
OverlappingNodesInIntervalTree<FPakInRequest>(
|
|
Pak.InRequests[Priority][(int32)EInRequestStatus::InFlight],
|
|
InRequestAllocator,
|
|
uint64(FirstByte),
|
|
uint64(FirstByte + Size - 1),
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[](TIntervalTreeIndex) -> bool
|
|
{
|
|
check(0); // if this is in flight, then why does it overlap my new block
|
|
return false;
|
|
}
|
|
);
|
|
OverlappingNodesInIntervalTree<FPakInRequest>(
|
|
Pak.InRequests[Priority][(int32)EInRequestStatus::Complete],
|
|
InRequestAllocator,
|
|
uint64(FirstByte),
|
|
uint64(FirstByte + Size - 1),
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[](TIntervalTreeIndex) -> bool
|
|
{
|
|
check(0); // if this is complete, then why does it overlap my new block
|
|
return false;
|
|
}
|
|
);
|
|
#endif
|
|
if (Priority == AIOP_MIN)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
for (TIntervalTreeIndex Fli : Inflights)
|
|
{
|
|
FPakInRequest& CompReq = InRequestAllocator.Get(Fli);
|
|
CompReq.Status = EInRequestStatus::InFlight;
|
|
AddToIntervalTree(&Pak.InRequests[CompReq.Priority][(int32)EInRequestStatus::InFlight], InRequestAllocator, Fli, Pak.StartShift, Pak.MaxShift);
|
|
}
|
|
|
|
StartBlockTask(Block);
|
|
return true;
|
|
|
|
}
|
|
|
|
int32 OpenTaskSlot()
|
|
{
|
|
int32 IndexToFill = -1;
|
|
for (int32 Index = 0; Index < GPakCache_MaxRequestsToLowerLevel; Index++)
|
|
{
|
|
if (!RequestsToLower[Index].RequestHandle)
|
|
{
|
|
IndexToFill = Index;
|
|
break;
|
|
}
|
|
}
|
|
return IndexToFill;
|
|
}
|
|
|
|
|
|
bool HasRequestsAtStatus(EInRequestStatus Status)
|
|
{
|
|
for (uint16 PakIndex = 0; PakIndex < CachedPakData.Num(); PakIndex++)
|
|
{
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
for (EAsyncIOPriority Priority = AIOP_MAX;; Priority = EAsyncIOPriority(int32(Priority) - 1))
|
|
{
|
|
if (Pak.InRequests[Priority][(int32)Status] != IntervalTreeInvalidIndex)
|
|
{
|
|
return true;
|
|
}
|
|
if (Priority == AIOP_MIN)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CanStartAnotherTask()
|
|
{
|
|
if (OpenTaskSlot() < 0)
|
|
{
|
|
return false;
|
|
}
|
|
return HasRequestsAtStatus(EInRequestStatus::Waiting);
|
|
}
|
|
void ClearOldBlockTasks()
|
|
{
|
|
if (!NotifyRecursion)
|
|
{
|
|
for (IAsyncReadRequest* Elem : RequestsToDelete)
|
|
{
|
|
Elem->WaitCompletion();
|
|
delete Elem;
|
|
}
|
|
RequestsToDelete.Empty();
|
|
}
|
|
}
|
|
void StartBlockTask(FCacheBlock& Block)
|
|
{
|
|
// CachedFilesScopeLock is locked
|
|
int32 IndexToFill = OpenTaskSlot();
|
|
if (IndexToFill < 0)
|
|
{
|
|
check(0);
|
|
return;
|
|
}
|
|
EAsyncIOPriority Priority = AIOP_Normal; // the lower level requests are not prioritized at the moment
|
|
check(Block.Status == EBlockStatus::InFlight);
|
|
UE_LOG(LogPakFile, Verbose, TEXT("FPakReadRequest[%016llX, %016llX) StartBlockTask"), Block.OffsetAndPakIndex, Block.OffsetAndPakIndex + Block.Size);
|
|
uint16 PakIndex = GetRequestPakIndex(Block.OffsetAndPakIndex);
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
RequestsToLower[IndexToFill].BlockIndex = Block.Index;
|
|
check(&CacheBlockAllocator.Get(RequestsToLower[IndexToFill].BlockIndex) == &Block);
|
|
|
|
RequestsToLower[IndexToFill].RequestHandle = Pak.Handle->ReadRequest(GetRequestOffset(Block.OffsetAndPakIndex), Block.Size, Priority, &CallbackFromLower);
|
|
LastReadRequest = Block.OffsetAndPakIndex + Block.Size;
|
|
Loads++;
|
|
LoadSize += Block.Size;
|
|
}
|
|
|
|
void CompleteRequest(bool bWasCanceled, IAsyncReadRequest* Request, TIntervalTreeIndex BlockIndex)
|
|
{
|
|
FCacheBlock& Block = CacheBlockAllocator.Get(BlockIndex);
|
|
uint16 PakIndex = GetRequestPakIndex(Block.OffsetAndPakIndex);
|
|
int64 Offset = GetRequestOffset(Block.OffsetAndPakIndex);
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
check(!Block.Memory && Block.Size);
|
|
check(!bWasCanceled); // this is doable, but we need to transition requests back to waiting, inflight etc.
|
|
|
|
if (!RemoveFromIntervalTree<FCacheBlock>(&Pak.CacheBlocks[(int32)EBlockStatus::InFlight], CacheBlockAllocator, Block.Index, Pak.StartShift, Pak.MaxShift))
|
|
{
|
|
check(0);
|
|
}
|
|
|
|
if (Block.InRequestRefCount == 0 || bWasCanceled)
|
|
{
|
|
ClearBlock(Block);
|
|
}
|
|
else
|
|
{
|
|
Block.Memory = Request->GetReadResults();
|
|
BlockMemory += Block.Size;
|
|
INC_MEMORY_STAT_BY(STAT_PakCacheMem, Block.Size);
|
|
|
|
if (BlockMemory > BlockMemoryHighWater)
|
|
{
|
|
BlockMemoryHighWater = BlockMemory;
|
|
|
|
static int64 LastPrint = 0;
|
|
if (BlockMemoryHighWater / 1024 / 1024 != LastPrint)
|
|
{
|
|
LastPrint = BlockMemoryHighWater / 1024 / 1024;
|
|
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Precache HighWater %dMB\r\n"), int32(LastPrint));
|
|
}
|
|
}
|
|
Block.Status = EBlockStatus::Complete;
|
|
AddToIntervalTree<FCacheBlock>(
|
|
&Pak.CacheBlocks[(int32)EBlockStatus::Complete],
|
|
CacheBlockAllocator,
|
|
Block.Index,
|
|
Pak.StartShift,
|
|
Pak.MaxShift
|
|
);
|
|
TArray<TIntervalTreeIndex> Completeds;
|
|
for (EAsyncIOPriority Priority = AIOP_MAX;; Priority = EAsyncIOPriority(int32(Priority) - 1))
|
|
{
|
|
if (Pak.InRequests[Priority][(int32)EInRequestStatus::InFlight] != IntervalTreeInvalidIndex)
|
|
{
|
|
MaybeRemoveOverlappingNodesInIntervalTree<FPakInRequest>(
|
|
&Pak.InRequests[Priority][(int32)EInRequestStatus::InFlight],
|
|
InRequestAllocator,
|
|
uint64(Offset),
|
|
uint64(Offset + Block.Size - 1),
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this, &Completeds](TIntervalTreeIndex RequestIndex) -> bool
|
|
{
|
|
if (FirstUnfilledBlockForRequest(RequestIndex) == MAX_uint64)
|
|
{
|
|
InRequestAllocator.Get(RequestIndex).Next = IntervalTreeInvalidIndex;
|
|
Completeds.Add(RequestIndex);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
);
|
|
}
|
|
if (Priority == AIOP_MIN)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
for (TIntervalTreeIndex Comp : Completeds)
|
|
{
|
|
FPakInRequest& CompReq = InRequestAllocator.Get(Comp);
|
|
CompReq.Status = EInRequestStatus::Complete;
|
|
AddToIntervalTree(&Pak.InRequests[CompReq.Priority][(int32)EInRequestStatus::Complete], InRequestAllocator, Comp, Pak.StartShift, Pak.MaxShift);
|
|
NotifyComplete(Comp); // potentially scary recursion here
|
|
}
|
|
}
|
|
}
|
|
|
|
bool StartNextRequest()
|
|
{
|
|
if (CanStartAnotherTask())
|
|
{
|
|
return AddNewBlock();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint8* GetCompletedRequestData(FPakInRequest& DoneRequest)
|
|
{
|
|
// CachedFilesScopeLock is locked
|
|
check(DoneRequest.Status == EInRequestStatus::Complete);
|
|
uint16 PakIndex = GetRequestPakIndex(DoneRequest.OffsetAndPakIndex);
|
|
int64 Offset = GetRequestOffset(DoneRequest.OffsetAndPakIndex);
|
|
int64 Size = DoneRequest.Size;
|
|
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
check(Offset + DoneRequest.Size <= Pak.TotalSize && DoneRequest.Size > 0 && DoneRequest.Priority >= AIOP_MIN && DoneRequest.Priority <= AIOP_MAX && DoneRequest.Status == EInRequestStatus::Complete);
|
|
|
|
uint8* Result = (uint8*)FMemory::Malloc(Size);
|
|
int64 BytesCopied = 0;
|
|
|
|
#if 0 // this path removes the block in one pass, however, this is not what we want because it wrecks precaching, if we change back GetCompletedRequest needs to maybe start a new request and the logic of the IAsyncFile read needs to change
|
|
MaybeRemoveOverlappingNodesInIntervalTree<FCacheBlock>(
|
|
&Pak.CacheBlocks[(int32)EBlockStatus::Complete],
|
|
CacheBlockAllocator,
|
|
Offset,
|
|
Offset + Size - 1,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this, Offset, Size, &BytesCopied, Result, &Pak](TIntervalTreeIndex BlockIndex) -> bool
|
|
{
|
|
FCacheBlock &Block = CacheBlockAllocator.Get(BlockIndex);
|
|
int64 BlockOffset = GetRequestOffset(Block.OffsetAndPakIndex);
|
|
check(Block.Memory && Block.Size && BlockOffset >= 0 && BlockOffset + Block.Size <= Pak.TotalSize);
|
|
|
|
int64 OverlapStart = FMath::Max(Offset, BlockOffset);
|
|
int64 OverlapEnd = FMath::Min(Offset + Size, BlockOffset + Block.Size);
|
|
check(OverlapEnd > OverlapStart);
|
|
BytesCopied += OverlapEnd - OverlapStart;
|
|
FMemory::Memcpy(Result + OverlapStart - Offset, Block.Memory + OverlapStart - BlockOffset, OverlapEnd - OverlapStart);
|
|
check(Block.InRequestRefCount);
|
|
if (!--Block.InRequestRefCount)
|
|
{
|
|
ClearBlock(Block);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
);
|
|
|
|
if (!RemoveFromIntervalTree<FPakInRequest>(&Pak.InRequests[DoneRequest.Priority][(int32)EInRequestStatus::Complete], InRequestAllocator, DoneRequest.Index, Pak.StartShift, Pak.MaxShift))
|
|
{
|
|
check(0); // not found
|
|
}
|
|
ClearRequest(DoneRequest);
|
|
#else
|
|
OverlappingNodesInIntervalTree<FCacheBlock>(
|
|
Pak.CacheBlocks[(int32)EBlockStatus::Complete],
|
|
CacheBlockAllocator,
|
|
Offset,
|
|
Offset + Size - 1,
|
|
0,
|
|
Pak.MaxNode,
|
|
Pak.StartShift,
|
|
Pak.MaxShift,
|
|
[this, Offset, Size, &BytesCopied, Result, &Pak](TIntervalTreeIndex BlockIndex) -> bool
|
|
{
|
|
FCacheBlock &Block = CacheBlockAllocator.Get(BlockIndex);
|
|
int64 BlockOffset = GetRequestOffset(Block.OffsetAndPakIndex);
|
|
check(Block.Memory && Block.Size && BlockOffset >= 0 && BlockOffset + Block.Size <= Pak.TotalSize);
|
|
|
|
int64 OverlapStart = FMath::Max(Offset, BlockOffset);
|
|
int64 OverlapEnd = FMath::Min(Offset + Size, BlockOffset + Block.Size);
|
|
check(OverlapEnd > OverlapStart);
|
|
BytesCopied += OverlapEnd - OverlapStart;
|
|
FMemory::Memcpy(Result + OverlapStart - Offset, Block.Memory + OverlapStart - BlockOffset, OverlapEnd - OverlapStart);
|
|
return true;
|
|
}
|
|
);
|
|
#endif
|
|
check(BytesCopied == Size);
|
|
|
|
|
|
return Result;
|
|
}
|
|
|
|
///// Below here are the thread entrypoints
|
|
|
|
void NewRequestsToLowerComplete(bool bWasCanceled, IAsyncReadRequest* Request)
|
|
{
|
|
FScopeLock Lock(&CachedFilesScopeLock);
|
|
ClearOldBlockTasks();
|
|
NotifyRecursion++;
|
|
for (int32 Index = 0; Index < PAK_CACHE_MAX_REQUESTS; Index++) // we loop over them all in case GPakCache_MaxRequestsToLowerLevel is changing
|
|
{
|
|
if (RequestsToLower[Index].RequestHandle == Request)
|
|
{
|
|
CompleteRequest(bWasCanceled, Request, RequestsToLower[Index].BlockIndex);
|
|
RequestsToLower[Index].RequestHandle = nullptr;
|
|
RequestsToDelete.Add(Request);
|
|
RequestsToLower[Index].BlockIndex = IntervalTreeInvalidIndex;
|
|
StartNextRequest();
|
|
NotifyRecursion--;
|
|
return;
|
|
}
|
|
}
|
|
StartNextRequest();
|
|
NotifyRecursion--;
|
|
check(0); // not found?
|
|
}
|
|
|
|
public:
|
|
|
|
bool QueueRequest(IPakRequestor* Owner, FName File, int64 PakFileSize, int64 Offset, int64 Size, EAsyncIOPriority Priority)
|
|
{
|
|
check(Owner && File != NAME_None && Size > 0 && Offset >= 0 && Offset < PakFileSize && Priority >= AIOP_MIN && Priority <= AIOP_MAX);
|
|
FScopeLock Lock(&CachedFilesScopeLock);
|
|
uint16* PakIndexPtr = CachedPaks.Find(File);
|
|
if (!PakIndexPtr)
|
|
{
|
|
check(CachedPakData.Num() < MAX_uint16);
|
|
IAsyncReadFileHandle* Handle = LowerLevel->OpenAsyncRead(*File.ToString());
|
|
if (!Handle)
|
|
{
|
|
return false;
|
|
}
|
|
CachedPakData.Add(FPakData(Handle, File, PakFileSize));
|
|
PakIndexPtr = &CachedPaks.Add(File, CachedPakData.Num() - 1);
|
|
}
|
|
uint16 PakIndex = *PakIndexPtr;
|
|
FPakData& Pak = CachedPakData[PakIndex];
|
|
check(Pak.Name == File && Pak.TotalSize == PakFileSize);
|
|
|
|
TIntervalTreeIndex RequestIndex = InRequestAllocator.Alloc();
|
|
FPakInRequest& Request = InRequestAllocator.Get(RequestIndex);
|
|
FJoinedOffsetAndPakIndex RequestOffsetAndPakIndex = MakeJoinedRequest(PakIndex, Offset);
|
|
Request.OffsetAndPakIndex = RequestOffsetAndPakIndex;
|
|
Request.Size = Size;
|
|
Request.Priority = Priority;
|
|
Request.Status = EInRequestStatus::Waiting;
|
|
Request.Owner = Owner;
|
|
Request.UniqueID = NextUniqueID++;
|
|
Request.Index = RequestIndex;
|
|
check(Request.Next == IntervalTreeInvalidIndex);
|
|
Owner->OffsetAndPakIndex = Request.OffsetAndPakIndex;
|
|
Owner->UniqueID = Request.UniqueID;
|
|
Owner->InRequestIndex = RequestIndex;
|
|
check(!OutstandingRequests.Contains(Request.UniqueID));
|
|
OutstandingRequests.Add(Request.UniqueID, RequestIndex);
|
|
if (AddRequest(RequestIndex))
|
|
{
|
|
UE_LOG(LogPakFile, Verbose, TEXT("FPakReadRequest[%016llX, %016llX) QueueRequest HOT"), RequestOffsetAndPakIndex, RequestOffsetAndPakIndex + Request.Size);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogPakFile, Verbose, TEXT("FPakReadRequest[%016llX, %016llX) QueueRequest COLD"), RequestOffsetAndPakIndex, RequestOffsetAndPakIndex + Request.Size);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint8* GetCompletedRequest(IPakRequestor* Owner)
|
|
{
|
|
check(Owner);
|
|
FScopeLock Lock(&CachedFilesScopeLock);
|
|
ClearOldBlockTasks();
|
|
TIntervalTreeIndex RequestIndex = OutstandingRequests.FindRef(Owner->UniqueID);
|
|
static_assert(IntervalTreeInvalidIndex == 0, "FindRef will return 0 for something not found");
|
|
if (RequestIndex)
|
|
{
|
|
FPakInRequest& Request = InRequestAllocator.Get(RequestIndex);
|
|
check(Owner == Request.Owner && Request.Status == EInRequestStatus::Complete && Request.UniqueID == Request.Owner->UniqueID && RequestIndex == Request.Owner->InRequestIndex && Request.OffsetAndPakIndex == Request.Owner->OffsetAndPakIndex);
|
|
uint8* Result = GetCompletedRequestData(Request);
|
|
return Result;
|
|
}
|
|
return nullptr; // canceled
|
|
}
|
|
|
|
void CancelRequest(IPakRequestor* Owner)
|
|
{
|
|
check(Owner);
|
|
FScopeLock Lock(&CachedFilesScopeLock);
|
|
ClearOldBlockTasks();
|
|
TIntervalTreeIndex RequestIndex = OutstandingRequests.FindRef(Owner->UniqueID);
|
|
static_assert(IntervalTreeInvalidIndex == 0, "FindRef will return 0 for something not found");
|
|
if (RequestIndex)
|
|
{
|
|
bool bWasOver = !HasEnoughRoomForPrecache();
|
|
FPakInRequest& Request = InRequestAllocator.Get(RequestIndex);
|
|
check(Owner == Request.Owner && Request.UniqueID == Request.Owner->UniqueID && RequestIndex == Request.Owner->InRequestIndex && Request.OffsetAndPakIndex == Request.Owner->OffsetAndPakIndex);
|
|
RemoveRequest(RequestIndex);
|
|
bool bNowOver = !HasEnoughRoomForPrecache();
|
|
if (bWasOver && !bNowOver)
|
|
{
|
|
StartNextRequest();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsProbablyIdle() // nothing to prevent new requests from being made before I return
|
|
{
|
|
FScopeLock Lock(&CachedFilesScopeLock);
|
|
return !HasRequestsAtStatus(EInRequestStatus::Waiting) && !HasRequestsAtStatus(EInRequestStatus::InFlight);
|
|
}
|
|
|
|
// these are not threadsafe and should only be used for synthetic testing
|
|
uint64 GetLoadSize()
|
|
{
|
|
return LoadSize;
|
|
}
|
|
uint32 GetLoads()
|
|
{
|
|
return Loads;
|
|
}
|
|
uint32 GetFrees()
|
|
{
|
|
return Frees;
|
|
}
|
|
|
|
void DumpBlocks()
|
|
{
|
|
while (!FPakPrecacher::Get().IsProbablyIdle())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_WaitDumpBlocks);
|
|
FPlatformProcess::SleepNoStats(0.001f);
|
|
}
|
|
FScopeLock Lock(&CachedFilesScopeLock);
|
|
bool bDone = !HasRequestsAtStatus(EInRequestStatus::Waiting) && !HasRequestsAtStatus(EInRequestStatus::InFlight) && !HasRequestsAtStatus(EInRequestStatus::Complete);
|
|
|
|
if (!bDone)
|
|
{
|
|
UE_LOG(LogPakFile, Log, TEXT("PakCache has outstanding requests with %llu total memory."), BlockMemory);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogPakFile, Log, TEXT("PakCache has no outstanding requests with %llu total memory."), BlockMemory);
|
|
}
|
|
}
|
|
};
|
|
|
|
static void WaitPrecache(const TArray<FString>& Args)
|
|
{
|
|
uint32 Frees = FPakPrecacher::Get().GetFrees();
|
|
uint32 Loads = FPakPrecacher::Get().GetLoads();
|
|
uint64 LoadSize = FPakPrecacher::Get().GetLoadSize();
|
|
|
|
double StartTime = FPlatformTime::Seconds();
|
|
|
|
while (!FPakPrecacher::Get().IsProbablyIdle())
|
|
{
|
|
check(Frees == FPakPrecacher::Get().GetFrees()); // otherwise we are discarding things, which is not what we want for this synthetic test
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_WaitPrecache);
|
|
FPlatformProcess::SleepNoStats(0.001f);
|
|
}
|
|
Loads = FPakPrecacher::Get().GetLoads() - Loads;
|
|
LoadSize = FPakPrecacher::Get().GetLoadSize() - LoadSize;
|
|
float TimeSpent = FPlatformTime::Seconds() - StartTime;
|
|
float LoadSizeMB = float(LoadSize) / (1024.0f * 1024.0f);
|
|
float MBs = LoadSizeMB / TimeSpent;
|
|
UE_LOG(LogPakFile, Log, TEXT("Loaded %4d blocks (align %4dKB) totalling %7.2fMB in %4.2fs = %6.2fMB/s"), Loads, PAK_CACHE_GRANULARITY / 1024, LoadSizeMB, TimeSpent, MBs);
|
|
}
|
|
|
|
static FAutoConsoleCommand WaitPrecacheCmd(
|
|
TEXT("pak.WaitPrecache"),
|
|
TEXT("Debug command to wait on the pak precache."),
|
|
FConsoleCommandWithArgsDelegate::CreateStatic(&WaitPrecache)
|
|
);
|
|
|
|
static void FlushPrecache(const TArray<FString>& Args)
|
|
{
|
|
}
|
|
|
|
static FAutoConsoleCommand FlushPrecacheCmd(
|
|
TEXT("pak.FlushPrecache"),
|
|
TEXT("Does nothing (yet)."),
|
|
FConsoleCommandWithArgsDelegate::CreateStatic(&FlushPrecache)
|
|
);
|
|
static void DumpBlocks(const TArray<FString>& Args)
|
|
{
|
|
FPakPrecacher::Get().DumpBlocks();
|
|
}
|
|
|
|
static FAutoConsoleCommand DumpBlocksCmd(
|
|
TEXT("pak.DumpBlocks"),
|
|
TEXT("Debug command to spew the outstanding blocks."),
|
|
FConsoleCommandWithArgsDelegate::CreateStatic(&DumpBlocks)
|
|
);
|
|
|
|
static FCriticalSection FPakReadRequestEvent;
|
|
|
|
class FPakReadRequest : public IAsyncReadRequest, public IPakRequestor
|
|
{
|
|
int64 Offset;
|
|
int64 BytesToRead;
|
|
FEvent* WaitEvent;
|
|
EAsyncIOPriority Priority;
|
|
bool bRequestOutstanding;
|
|
bool bNeedsRemoval;
|
|
public:
|
|
FPakReadRequest(FName InPakFile, int64 PakFileSize, FAsyncFileCallBack* CompleteCallback, int64 InOffset, int64 InBytesToRead, EAsyncIOPriority InPriority)
|
|
: IAsyncReadRequest(CompleteCallback, false)
|
|
, Offset(InOffset)
|
|
, BytesToRead(InBytesToRead)
|
|
, WaitEvent(nullptr)
|
|
, Priority(InPriority)
|
|
, bRequestOutstanding(true)
|
|
, bNeedsRemoval(true)
|
|
{
|
|
check(Offset >= 0 && BytesToRead > 0);
|
|
if (!FPakPrecacher::Get().QueueRequest(this, InPakFile, PakFileSize, Offset, BytesToRead, Priority))
|
|
{
|
|
bRequestOutstanding = false;
|
|
SetComplete();
|
|
}
|
|
}
|
|
|
|
virtual ~FPakReadRequest()
|
|
{
|
|
if (bNeedsRemoval)
|
|
{
|
|
FPakPrecacher::Get().CancelRequest(this);
|
|
}
|
|
}
|
|
|
|
// IAsyncReadRequest Interface
|
|
|
|
virtual void WaitCompletionImpl(float TimeLimitSeconds) override
|
|
{
|
|
if (bRequestOutstanding)
|
|
{
|
|
{
|
|
FScopeLock Lock(&FPakReadRequestEvent);
|
|
if (bRequestOutstanding)
|
|
{
|
|
check(!WaitEvent);
|
|
WaitEvent = FPlatformProcess::GetSynchEventFromPool(true);
|
|
}
|
|
}
|
|
if (WaitEvent)
|
|
{
|
|
if (TimeLimitSeconds == 0.0f)
|
|
{
|
|
WaitEvent->Wait();
|
|
check(!bRequestOutstanding);
|
|
}
|
|
else
|
|
{
|
|
WaitEvent->Wait(TimeLimitSeconds * 1000.0f);
|
|
}
|
|
FPlatformProcess::ReturnSynchEventToPool(WaitEvent);
|
|
WaitEvent = nullptr;
|
|
}
|
|
}
|
|
}
|
|
virtual void CancelImpl() override
|
|
{
|
|
check(!WaitEvent); // you canceled from a different thread that you waited from
|
|
FPakPrecacher::Get().CancelRequest(this);
|
|
bNeedsRemoval = false;
|
|
if (bRequestOutstanding)
|
|
{
|
|
bRequestOutstanding = false;
|
|
SetComplete();
|
|
}
|
|
check(!WaitEvent); // you canceled from a different thread that you waited from
|
|
}
|
|
|
|
// IPakRequestor Interface
|
|
|
|
virtual void RequestIsComplete() override
|
|
{
|
|
check(bRequestOutstanding && !Memory);
|
|
if (bCanceled || Priority <= AIOP_Precache)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
Memory = FPakPrecacher::Get().GetCompletedRequest(this);
|
|
// Memory could be null here if the request was canceled
|
|
}
|
|
SetComplete();
|
|
FScopeLock Lock(&FPakReadRequestEvent);
|
|
bRequestOutstanding = false;
|
|
if (WaitEvent)
|
|
{
|
|
WaitEvent->Trigger();
|
|
}
|
|
}
|
|
};
|
|
|
|
class FPakSizeRequest : public IAsyncReadRequest
|
|
{
|
|
public:
|
|
FPakSizeRequest(FAsyncFileCallBack* CompleteCallback, int64 InFileSize)
|
|
: IAsyncReadRequest(CompleteCallback, true)
|
|
{
|
|
Size = InFileSize;
|
|
SetComplete();
|
|
}
|
|
virtual void WaitCompletionImpl(float TimeLimitSeconds) override
|
|
{
|
|
}
|
|
virtual void CancelImpl()
|
|
{
|
|
}
|
|
};
|
|
|
|
|
|
class FPakAsyncReadFileHandle final : public IAsyncReadFileHandle
|
|
{
|
|
FName PakFile;
|
|
int64 PakFileSize;
|
|
int64 OffsetInPak;
|
|
int64 FileSize;
|
|
public:
|
|
FPakAsyncReadFileHandle(FName InPakFile, int64 InPakFileSize, int64 InOffsetInPak, int64 InFileSize)
|
|
: PakFile(InPakFile)
|
|
, PakFileSize(InPakFileSize)
|
|
, OffsetInPak(InOffsetInPak)
|
|
, FileSize(InFileSize)
|
|
{
|
|
check(PakFileSize > 0 && OffsetInPak + FileSize <= PakFileSize && OffsetInPak >= 0);
|
|
}
|
|
virtual IAsyncReadRequest* SizeRequest(FAsyncFileCallBack* CompleteCallback = nullptr) override
|
|
{
|
|
return new FPakSizeRequest(CompleteCallback, FileSize);
|
|
}
|
|
virtual IAsyncReadRequest* ReadRequest(int64 Offset, int64 BytesToRead, EAsyncIOPriority Priority = AIOP_Normal, FAsyncFileCallBack* CompleteCallback = nullptr) override
|
|
{
|
|
if (BytesToRead == MAX_int64)
|
|
{
|
|
BytesToRead = FileSize - Offset;
|
|
}
|
|
check(Offset + BytesToRead <= FileSize && Offset >= 0 && Offset + BytesToRead + OffsetInPak <= PakFileSize);
|
|
return new FPakReadRequest(PakFile, PakFileSize, CompleteCallback, OffsetInPak + Offset, BytesToRead, Priority);
|
|
}
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
#if USE_NEW_ASYNC_IO
|
|
|
|
|
|
IAsyncReadFileHandle* FPakPlatformFile::OpenAsyncRead(const TCHAR* Filename)
|
|
{
|
|
#if USE_PAK_PRECACHE
|
|
if (GPakCache_Enable > 0)
|
|
{
|
|
FPakFile* PakFile = NULL;
|
|
const FPakEntry* FileEntry = FindFileInPakFiles(Filename, &PakFile);
|
|
if (FileEntry && PakFile && PakFile->GetFilenameName() != NAME_None)
|
|
{
|
|
int64 OffsetToFile = FileEntry->Offset + FileEntry->GetSerializedSize(PakFile->GetInfo().Version);
|
|
UE_LOG(LogPakFile, Verbose, TEXT("FPakPlatformFile::OpenAsyncRead[%016llX, %016llX) %s"), OffsetToFile, OffsetToFile + FileEntry->UncompressedSize, Filename);
|
|
return new FPakAsyncReadFileHandle(PakFile->GetFilenameName(), PakFile->TotalSize(), OffsetToFile, FileEntry->UncompressedSize);
|
|
}
|
|
}
|
|
#endif
|
|
return IPlatformFile::OpenAsyncRead(Filename);
|
|
}
|
|
|
|
#endif // USE_NEW_ASYNC_IO
|
|
|
|
/**
|
|
* Class to handle correctly reading from a compressed file within a compressed package
|
|
*/
|
|
class FPakSimpleEncryption
|
|
{
|
|
public:
|
|
enum
|
|
{
|
|
Alignment = FAES::AESBlockSize,
|
|
};
|
|
|
|
static FORCEINLINE int64 AlignReadRequest(int64 Size)
|
|
{
|
|
return Align(Size, Alignment);
|
|
}
|
|
|
|
static FORCEINLINE void DecryptBlock(void* Data, int64 Size)
|
|
{
|
|
#ifdef AES_KEY
|
|
FAES::DecryptData((uint8*)Data, Size);
|
|
#endif
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Thread local class to manage working buffers for file compression
|
|
*/
|
|
class FCompressionScratchBuffers : public TThreadSingleton<FCompressionScratchBuffers>
|
|
{
|
|
public:
|
|
FCompressionScratchBuffers()
|
|
: TempBufferSize(0)
|
|
, ScratchBufferSize(0)
|
|
{}
|
|
|
|
int64 TempBufferSize;
|
|
TAutoPtr<uint8> TempBuffer;
|
|
int64 ScratchBufferSize;
|
|
TAutoPtr<uint8> ScratchBuffer;
|
|
|
|
void EnsureBufferSpace(int64 CompressionBlockSize, int64 ScrachSize)
|
|
{
|
|
if(TempBufferSize < CompressionBlockSize)
|
|
{
|
|
TempBufferSize = CompressionBlockSize;
|
|
TempBuffer.Reset((uint8*)FMemory::Malloc(TempBufferSize));
|
|
}
|
|
if(ScratchBufferSize < ScrachSize)
|
|
{
|
|
ScratchBufferSize = ScrachSize;
|
|
ScratchBuffer.Reset((uint8*)FMemory::Malloc(ScratchBufferSize));
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Class to handle correctly reading from a compressed file within a pak
|
|
*/
|
|
template< typename EncryptionPolicy = FPakNoEncryption >
|
|
class FPakCompressedReaderPolicy
|
|
{
|
|
public:
|
|
class FPakUncompressTask : public FNonAbandonableTask
|
|
{
|
|
public:
|
|
uint8* UncompressedBuffer;
|
|
int32 UncompressedSize;
|
|
uint8* CompressedBuffer;
|
|
int32 CompressedSize;
|
|
ECompressionFlags Flags;
|
|
void* CopyOut;
|
|
int64 CopyOffset;
|
|
int64 CopyLength;
|
|
|
|
void DoWork()
|
|
{
|
|
// Decrypt and Uncompress from memory to memory.
|
|
int64 EncryptionSize = EncryptionPolicy::AlignReadRequest(CompressedSize);
|
|
EncryptionPolicy::DecryptBlock(CompressedBuffer, EncryptionSize);
|
|
FCompression::UncompressMemory(Flags, UncompressedBuffer, UncompressedSize, CompressedBuffer, CompressedSize, false, FPlatformMisc::GetPlatformCompression()->GetCompressionBitWindow());
|
|
if (CopyOut)
|
|
{
|
|
FMemory::Memcpy(CopyOut, UncompressedBuffer+CopyOffset, CopyLength);
|
|
}
|
|
}
|
|
|
|
FORCEINLINE TStatId GetStatId() const
|
|
{
|
|
// TODO: This is called too early in engine startup.
|
|
return TStatId();
|
|
//RETURN_QUICK_DECLARE_CYCLE_STAT(FPakUncompressTask, STATGROUP_ThreadPoolAsyncTasks);
|
|
}
|
|
};
|
|
|
|
FPakCompressedReaderPolicy(const FPakFile& InPakFile, const FPakEntry& InPakEntry, FArchive* InPakReader)
|
|
: PakFile(InPakFile)
|
|
, PakEntry(InPakEntry)
|
|
, PakReader(InPakReader)
|
|
{
|
|
}
|
|
|
|
/** Pak file that own this file data */
|
|
const FPakFile& PakFile;
|
|
/** Pak file entry for this file. */
|
|
const FPakEntry& PakEntry;
|
|
/** Pak file archive to read the data from. */
|
|
FArchive* PakReader;
|
|
|
|
FORCEINLINE int64 FileSize() const
|
|
{
|
|
return PakEntry.UncompressedSize;
|
|
}
|
|
|
|
void Serialize(int64 DesiredPosition, void* V, int64 Length)
|
|
{
|
|
const int32 CompressionBlockSize = PakEntry.CompressionBlockSize;
|
|
uint32 CompressionBlockIndex = DesiredPosition / CompressionBlockSize;
|
|
uint8* WorkingBuffers[2];
|
|
int64 DirectCopyStart = DesiredPosition % PakEntry.CompressionBlockSize;
|
|
FAsyncTask<FPakUncompressTask> UncompressTask;
|
|
FCompressionScratchBuffers& ScratchSpace = FCompressionScratchBuffers::Get();
|
|
bool bStartedUncompress = false;
|
|
|
|
int64 WorkingBufferRequiredSize = FCompression::CompressMemoryBound((ECompressionFlags)PakEntry.CompressionMethod,CompressionBlockSize, FPlatformMisc::GetPlatformCompression()->GetCompressionBitWindow());
|
|
WorkingBufferRequiredSize = EncryptionPolicy::AlignReadRequest(WorkingBufferRequiredSize);
|
|
ScratchSpace.EnsureBufferSpace(CompressionBlockSize, WorkingBufferRequiredSize*2);
|
|
WorkingBuffers[0] = ScratchSpace.ScratchBuffer;
|
|
WorkingBuffers[1] = ScratchSpace.ScratchBuffer + WorkingBufferRequiredSize;
|
|
|
|
while (Length > 0)
|
|
{
|
|
const FPakCompressedBlock& Block = PakEntry.CompressionBlocks[CompressionBlockIndex];
|
|
int64 Pos = CompressionBlockIndex * CompressionBlockSize;
|
|
int64 CompressedBlockSize = Block.CompressedEnd-Block.CompressedStart;
|
|
int64 UncompressedBlockSize = FMath::Min<int64>(PakEntry.UncompressedSize-Pos, PakEntry.CompressionBlockSize);
|
|
int64 ReadSize = EncryptionPolicy::AlignReadRequest(CompressedBlockSize);
|
|
int64 WriteSize = FMath::Min<int64>(UncompressedBlockSize - DirectCopyStart, Length);
|
|
PakReader->Seek(Block.CompressedStart);
|
|
PakReader->Serialize(WorkingBuffers[CompressionBlockIndex & 1],ReadSize);
|
|
if (bStartedUncompress)
|
|
{
|
|
UncompressTask.EnsureCompletion();
|
|
bStartedUncompress = false;
|
|
}
|
|
|
|
FPakUncompressTask& TaskDetails = UncompressTask.GetTask();
|
|
if (DirectCopyStart == 0 && Length >= CompressionBlockSize)
|
|
{
|
|
// Block can be decompressed directly into output buffer
|
|
TaskDetails.Flags = (ECompressionFlags)PakEntry.CompressionMethod;
|
|
TaskDetails.UncompressedBuffer = (uint8*)V;
|
|
TaskDetails.UncompressedSize = UncompressedBlockSize;
|
|
TaskDetails.CompressedBuffer = WorkingBuffers[CompressionBlockIndex & 1];
|
|
TaskDetails.CompressedSize = CompressedBlockSize;
|
|
TaskDetails.CopyOut = nullptr;
|
|
}
|
|
else
|
|
{
|
|
// Block needs to be copied from a working buffer
|
|
TaskDetails.Flags = (ECompressionFlags)PakEntry.CompressionMethod;
|
|
TaskDetails.UncompressedBuffer = (uint8*)ScratchSpace.TempBuffer;
|
|
TaskDetails.UncompressedSize = UncompressedBlockSize;
|
|
TaskDetails.CompressedBuffer = WorkingBuffers[CompressionBlockIndex & 1];
|
|
TaskDetails.CompressedSize = CompressedBlockSize;
|
|
TaskDetails.CopyOut = V;
|
|
TaskDetails.CopyOffset = DirectCopyStart;
|
|
TaskDetails.CopyLength = WriteSize;
|
|
}
|
|
|
|
if (Length == WriteSize)
|
|
{
|
|
UncompressTask.StartSynchronousTask();
|
|
}
|
|
else
|
|
{
|
|
UncompressTask.StartBackgroundTask();
|
|
}
|
|
bStartedUncompress = true;
|
|
V = (void*)((uint8*)V + WriteSize);
|
|
Length -= WriteSize;
|
|
DirectCopyStart = 0;
|
|
++CompressionBlockIndex;
|
|
}
|
|
|
|
if(bStartedUncompress)
|
|
{
|
|
UncompressTask.EnsureCompletion();
|
|
}
|
|
}
|
|
};
|
|
|
|
bool FPakEntry::VerifyPakEntriesMatch(const FPakEntry& FileEntryA, const FPakEntry& FileEntryB)
|
|
{
|
|
bool bResult = true;
|
|
if (FileEntryA.Size != FileEntryB.Size)
|
|
{
|
|
UE_LOG(LogPakFile, Error, TEXT("Pak header file size mismatch, got: %lld, expected: %lld"), FileEntryB.Size, FileEntryA.Size);
|
|
bResult = false;
|
|
}
|
|
if (FileEntryA.UncompressedSize != FileEntryB.UncompressedSize)
|
|
{
|
|
UE_LOG(LogPakFile, Error, TEXT("Pak header uncompressed file size mismatch, got: %lld, expected: %lld"), FileEntryB.UncompressedSize, FileEntryA.UncompressedSize);
|
|
bResult = false;
|
|
}
|
|
if (FileEntryA.CompressionMethod != FileEntryB.CompressionMethod)
|
|
{
|
|
UE_LOG(LogPakFile, Error, TEXT("Pak header file compression method mismatch, got: %d, expected: %d"), FileEntryB.CompressionMethod, FileEntryA.CompressionMethod);
|
|
bResult = false;
|
|
}
|
|
if (FMemory::Memcmp(FileEntryA.Hash, FileEntryB.Hash, sizeof(FileEntryA.Hash)) != 0)
|
|
{
|
|
UE_LOG(LogPakFile, Error, TEXT("Pak file hash does not match its index entry"));
|
|
bResult = false;
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
bool FPakPlatformFile::IsNonPakFilenameAllowed(const FString& InFilename)
|
|
{
|
|
FFilenameSecurityDelegate& FilenameSecurityDelegate = GetFilenameSecurityDelegate();
|
|
|
|
if (!FilenameSecurityDelegate.IsBound())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (GetLowerLevel()->FileExists(*InFilename))
|
|
{
|
|
return FilenameSecurityDelegate.Execute(*InFilename);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FPakFile::FPakFile(const TCHAR* Filename, bool bIsSigned)
|
|
: PakFilename(Filename)
|
|
, PakFilenameName(Filename)
|
|
, CachedTotalSize(0)
|
|
, bSigned(bIsSigned)
|
|
, bIsValid(false)
|
|
{
|
|
FArchive* Reader = GetSharedReader(NULL);
|
|
if (Reader)
|
|
{
|
|
Timestamp = IFileManager::Get().GetTimeStamp(Filename);
|
|
Initialize(Reader);
|
|
}
|
|
}
|
|
|
|
FPakFile::FPakFile(IPlatformFile* LowerLevel, const TCHAR* Filename, bool bIsSigned)
|
|
: PakFilename(Filename)
|
|
, PakFilenameName(Filename)
|
|
, CachedTotalSize(0)
|
|
, bSigned(bIsSigned)
|
|
, bIsValid(false)
|
|
{
|
|
FArchive* Reader = GetSharedReader(LowerLevel);
|
|
if (Reader)
|
|
{
|
|
Timestamp = LowerLevel->GetTimeStamp(Filename);
|
|
Initialize(Reader);
|
|
}
|
|
}
|
|
|
|
FPakFile::FPakFile(FArchive* Archive)
|
|
: bSigned(false)
|
|
, bIsValid(false)
|
|
{
|
|
Initialize(Archive);
|
|
}
|
|
|
|
FPakFile::~FPakFile()
|
|
{
|
|
}
|
|
|
|
FArchive* FPakFile::CreatePakReader(const TCHAR* Filename)
|
|
{
|
|
FArchive* ReaderArchive = IFileManager::Get().CreateFileReader(Filename);
|
|
return SetupSignedPakReader(ReaderArchive, Filename);
|
|
}
|
|
|
|
FArchive* FPakFile::CreatePakReader(IFileHandle& InHandle, const TCHAR* Filename)
|
|
{
|
|
FArchive* ReaderArchive = new FArchiveFileReaderGeneric(&InHandle, Filename, InHandle.Size());
|
|
return SetupSignedPakReader(ReaderArchive, Filename);
|
|
}
|
|
|
|
FArchive* FPakFile::SetupSignedPakReader(FArchive* ReaderArchive, const TCHAR* Filename)
|
|
{
|
|
if (FPlatformProperties::RequiresCookedData())
|
|
{
|
|
#if !USING_SIGNED_CONTENT
|
|
if (bSigned || FParse::Param(FCommandLine::Get(), TEXT("signedpak")) || FParse::Param(FCommandLine::Get(), TEXT("signed")))
|
|
#endif
|
|
{
|
|
if (!Decryptor.IsValid())
|
|
{
|
|
Decryptor = new FChunkCacheWorker(ReaderArchive, Filename);
|
|
}
|
|
ReaderArchive = new FSignedArchiveReader(ReaderArchive, Decryptor);
|
|
}
|
|
}
|
|
return ReaderArchive;
|
|
}
|
|
|
|
void FPakFile::Initialize(FArchive* Reader)
|
|
{
|
|
CachedTotalSize = Reader->TotalSize();
|
|
|
|
if (CachedTotalSize < Info.GetSerializedSize())
|
|
{
|
|
UE_LOG(LogPakFile, Fatal, TEXT("Corrupted pak file '%s' (too short). Verify your installation."), *PakFilename);
|
|
}
|
|
else
|
|
{
|
|
// Serialize trailer and check if everything is as expected.
|
|
Reader->Seek(CachedTotalSize - Info.GetSerializedSize());
|
|
Info.Serialize(*Reader);
|
|
UE_CLOG(Info.Magic != FPakInfo::PakFile_Magic, LogPakFile, Fatal, TEXT("Trailing magic number (%ud) in '%s' is different than the expected one. Verify your installation."), Info.Magic, *PakFilename);
|
|
UE_CLOG(!(Info.Version >= FPakInfo::PakFile_Version_Initial && Info.Version <= FPakInfo::PakFile_Version_Latest), LogPakFile, Fatal, TEXT("Invalid pak file version (%d) in '%s'. Verify your installation."), Info.Version, *PakFilename);
|
|
|
|
LoadIndex(Reader);
|
|
// LoadIndex should crash in case of an error, so just assume everything is ok if we got here.
|
|
bIsValid = true;
|
|
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("checkpak")))
|
|
{
|
|
ensure(Check());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPakFile::LoadIndex(FArchive* Reader)
|
|
{
|
|
if (CachedTotalSize < (Info.IndexOffset + Info.IndexSize))
|
|
{
|
|
UE_LOG(LogPakFile, Fatal, TEXT("Corrupted index offset in pak file."));
|
|
}
|
|
else
|
|
{
|
|
// Load index into memory first.
|
|
Reader->Seek(Info.IndexOffset);
|
|
TArray<uint8> IndexData;
|
|
IndexData.AddUninitialized(Info.IndexSize);
|
|
Reader->Serialize(IndexData.GetData(), Info.IndexSize);
|
|
FMemoryReader IndexReader(IndexData);
|
|
|
|
// Check SHA1 value.
|
|
uint8 IndexHash[20];
|
|
FSHA1::HashBuffer(IndexData.GetData(), IndexData.Num(), IndexHash);
|
|
if (FMemory::Memcmp(IndexHash, Info.IndexHash, sizeof(IndexHash)) != 0)
|
|
{
|
|
UE_LOG(LogPakFile, Fatal, TEXT("Corrupted index in pak file (CRC mismatch)."));
|
|
}
|
|
|
|
// Read the default mount point and all entries.
|
|
int32 NumEntries = 0;
|
|
IndexReader << MountPoint;
|
|
IndexReader << NumEntries;
|
|
|
|
MakeDirectoryFromPath(MountPoint);
|
|
// Allocate enough memory to hold all entries (and not reallocate while they're being added to it).
|
|
Files.Empty(NumEntries);
|
|
|
|
for (int32 EntryIndex = 0; EntryIndex < NumEntries; EntryIndex++)
|
|
{
|
|
// Serialize from memory.
|
|
FPakEntry Entry;
|
|
FString Filename;
|
|
IndexReader << Filename;
|
|
Entry.Serialize(IndexReader, Info.Version);
|
|
|
|
// Add new file info.
|
|
Files.Add(Entry);
|
|
|
|
// Construct Index of all directories in pak file.
|
|
FString Path = FPaths::GetPath(Filename);
|
|
MakeDirectoryFromPath(Path);
|
|
FPakDirectory* Directory = Index.Find(Path);
|
|
if (Directory != NULL)
|
|
{
|
|
Directory->Add(Filename, &Files.Last());
|
|
}
|
|
else
|
|
{
|
|
FPakDirectory NewDirectory;
|
|
NewDirectory.Add(Filename, &Files.Last());
|
|
Index.Add(Path, NewDirectory);
|
|
|
|
// add the parent directories up to the mount point
|
|
while (MountPoint != Path)
|
|
{
|
|
Path = Path.Left(Path.Len()-1);
|
|
int32 Offset = 0;
|
|
if (Path.FindLastChar('/', Offset))
|
|
{
|
|
Path = Path.Left(Offset);
|
|
MakeDirectoryFromPath(Path);
|
|
if (Index.Find(Path) == NULL)
|
|
{
|
|
FPakDirectory ParentDirectory;
|
|
Index.Add(Path, ParentDirectory);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Path = MountPoint;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FPakFile::Check()
|
|
{
|
|
UE_LOG(LogPakFile, Display, TEXT("Checking pak file \"%s\". This may take a while..."), *PakFilename);
|
|
FArchive& PakReader = *GetSharedReader(NULL);
|
|
int32 ErrorCount = 0;
|
|
int32 FileCount = 0;
|
|
|
|
for (FPakFile::FFileIterator It(*this); It; ++It, ++FileCount)
|
|
{
|
|
const FPakEntry& Entry = It.Info();
|
|
void* FileContents = FMemory::Malloc(Entry.Size);
|
|
PakReader.Seek(Entry.Offset);
|
|
uint32 SerializedCrcTest = 0;
|
|
FPakEntry EntryInfo;
|
|
EntryInfo.Serialize(PakReader, GetInfo().Version);
|
|
if (EntryInfo != Entry)
|
|
{
|
|
UE_LOG(LogPakFile, Error, TEXT("Serialized hash mismatch for \"%s\"."), *It.Filename());
|
|
ErrorCount++;
|
|
}
|
|
PakReader.Serialize(FileContents, Entry.Size);
|
|
|
|
uint8 TestHash[20];
|
|
FSHA1::HashBuffer(FileContents, Entry.Size, TestHash);
|
|
if (FMemory::Memcmp(TestHash, Entry.Hash, sizeof(TestHash)) != 0)
|
|
{
|
|
UE_LOG(LogPakFile, Error, TEXT("Hash mismatch for \"%s\"."), *It.Filename());
|
|
ErrorCount++;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogPakFile, Display, TEXT("\"%s\" OK."), *It.Filename());
|
|
}
|
|
FMemory::Free(FileContents);
|
|
}
|
|
if (ErrorCount == 0)
|
|
{
|
|
UE_LOG(LogPakFile, Display, TEXT("Pak file \"%s\" healthy, %d files checked."), *PakFilename, FileCount);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogPakFile, Display, TEXT("Pak file \"%s\" corrupted (%d errors ouf of %d files checked.)."), *PakFilename, ErrorCount, FileCount);
|
|
}
|
|
|
|
return ErrorCount == 0;
|
|
}
|
|
|
|
FArchive* FPakFile::GetSharedReader(IPlatformFile* LowerLevel)
|
|
{
|
|
uint32 Thread = FPlatformTLS::GetCurrentThreadId();
|
|
FArchive* PakReader = NULL;
|
|
{
|
|
FScopeLock ScopedLock(&CriticalSection);
|
|
TAutoPtr<FArchive>* ExistingReader = ReaderMap.Find(Thread);
|
|
if (ExistingReader)
|
|
{
|
|
PakReader = *ExistingReader;
|
|
}
|
|
}
|
|
if (!PakReader)
|
|
{
|
|
// Create a new FArchive reader and pass it to the new handle.
|
|
if (LowerLevel != NULL)
|
|
{
|
|
IFileHandle* PakHandle = LowerLevel->OpenRead(*GetFilename());
|
|
if (PakHandle)
|
|
{
|
|
PakReader = CreatePakReader(*PakHandle, *GetFilename());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PakReader = CreatePakReader(*GetFilename());
|
|
}
|
|
if (!PakReader)
|
|
{
|
|
UE_LOG(LogPakFile, Fatal, TEXT("Unable to create pak \"%s\" handle"), *GetFilename());
|
|
}
|
|
{
|
|
FScopeLock ScopedLock(&CriticalSection);
|
|
ReaderMap.Emplace(Thread, PakReader);
|
|
}
|
|
}
|
|
return PakReader;
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
class FPakExec : private FSelfRegisteringExec
|
|
{
|
|
FPakPlatformFile& PlatformFile;
|
|
|
|
public:
|
|
|
|
FPakExec(FPakPlatformFile& InPlatformFile)
|
|
: PlatformFile(InPlatformFile)
|
|
{}
|
|
|
|
/** Console commands **/
|
|
virtual bool Exec( UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar ) override
|
|
{
|
|
if (FParse::Command(&Cmd, TEXT("Mount")))
|
|
{
|
|
PlatformFile.HandleMountCommand(Cmd, Ar);
|
|
return true;
|
|
}
|
|
if (FParse::Command(&Cmd, TEXT("Unmount")))
|
|
{
|
|
PlatformFile.HandleUnmountCommand(Cmd, Ar);
|
|
return true;
|
|
}
|
|
else if (FParse::Command(&Cmd, TEXT("PakList")))
|
|
{
|
|
PlatformFile.HandlePakListCommand(Cmd, Ar);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
static TAutoPtr<FPakExec> GPakExec;
|
|
|
|
void FPakPlatformFile::HandleMountCommand(const TCHAR* Cmd, FOutputDevice& Ar)
|
|
{
|
|
const FString PakFilename = FParse::Token(Cmd, false);
|
|
if (!PakFilename.IsEmpty())
|
|
{
|
|
const FString MountPoint = FParse::Token(Cmd, false);
|
|
Mount(*PakFilename, 0, MountPoint.IsEmpty() ? NULL : *MountPoint);
|
|
}
|
|
}
|
|
|
|
void FPakPlatformFile::HandleUnmountCommand(const TCHAR* Cmd, FOutputDevice& Ar)
|
|
{
|
|
const FString PakFilename = FParse::Token(Cmd, false);
|
|
if (!PakFilename.IsEmpty())
|
|
{
|
|
Unmount(*PakFilename);
|
|
}
|
|
}
|
|
|
|
void FPakPlatformFile::HandlePakListCommand(const TCHAR* Cmd, FOutputDevice& Ar)
|
|
{
|
|
TArray<FPakListEntry> Paks;
|
|
GetMountedPaks(Paks);
|
|
for (auto Pak : Paks)
|
|
{
|
|
Ar.Logf(TEXT("%s"), *Pak.PakFile->GetFilename());
|
|
}
|
|
}
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
FPakPlatformFile::FPakPlatformFile()
|
|
: LowerLevel(NULL)
|
|
, bSigned(false)
|
|
{
|
|
}
|
|
|
|
FPakPlatformFile::~FPakPlatformFile()
|
|
{
|
|
FCoreDelegates::OnMountPak.Unbind();
|
|
FCoreDelegates::OnUnmountPak.Unbind();
|
|
|
|
// We need to flush async IO... if it hasn't been shut down already.
|
|
if (FIOSystem::HasShutdown() == false)
|
|
{
|
|
FIOSystem& IOSystem = FIOSystem::Get();
|
|
IOSystem.BlockTillAllRequestsFinishedAndFlushHandles();
|
|
}
|
|
|
|
{
|
|
FScopeLock ScopedLock(&PakListCritical);
|
|
for (int32 PakFileIndex = 0; PakFileIndex < PakFiles.Num(); PakFileIndex++)
|
|
{
|
|
delete PakFiles[PakFileIndex].PakFile;
|
|
PakFiles[PakFileIndex].PakFile = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPakPlatformFile::FindPakFilesInDirectory(IPlatformFile* LowLevelFile, const TCHAR* Directory, TArray<FString>& OutPakFiles)
|
|
{
|
|
// Helper class to find all pak files.
|
|
class FPakSearchVisitor : public IPlatformFile::FDirectoryVisitor
|
|
{
|
|
TArray<FString>& FoundPakFiles;
|
|
IPlatformChunkInstall* ChunkInstall;
|
|
public:
|
|
FPakSearchVisitor(TArray<FString>& InFoundPakFiles, IPlatformChunkInstall* InChunkInstall)
|
|
: FoundPakFiles(InFoundPakFiles)
|
|
, ChunkInstall(InChunkInstall)
|
|
{}
|
|
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory)
|
|
{
|
|
if (bIsDirectory == false)
|
|
{
|
|
FString Filename(FilenameOrDirectory);
|
|
if (FPaths::GetExtension(Filename) == TEXT("pak"))
|
|
{
|
|
// if a platform supports chunk style installs, make sure that the chunk a pak file resides in is actually fully installed before accepting pak files from it
|
|
if (ChunkInstall)
|
|
{
|
|
FString ChunkIdentifier(TEXT("pakchunk"));
|
|
FString BaseFilename = FPaths::GetBaseFilename(Filename);
|
|
if (BaseFilename.StartsWith(ChunkIdentifier))
|
|
{
|
|
int32 DelimiterIndex = 0;
|
|
int32 StartOfChunkIndex = ChunkIdentifier.Len();
|
|
|
|
BaseFilename.FindChar(TEXT('-'), DelimiterIndex);
|
|
FString ChunkNumberString = BaseFilename.Mid(StartOfChunkIndex, DelimiterIndex-StartOfChunkIndex);
|
|
int32 ChunkNumber = 0;
|
|
TTypeFromString<int32>::FromString(ChunkNumber, *ChunkNumberString);
|
|
if (ChunkInstall->GetChunkLocation(ChunkNumber) == EChunkLocation::NotAvailable)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
FoundPakFiles.Add(Filename);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
// Find all pak files.
|
|
FPakSearchVisitor Visitor(OutPakFiles, FPlatformMisc::GetPlatformChunkInstall());
|
|
LowLevelFile->IterateDirectoryRecursively(Directory, Visitor);
|
|
}
|
|
|
|
void FPakPlatformFile::FindAllPakFiles(IPlatformFile* LowLevelFile, const TArray<FString>& PakFolders, TArray<FString>& OutPakFiles)
|
|
{
|
|
// Find pak files from the specified directories.
|
|
for (int32 FolderIndex = 0; FolderIndex < PakFolders.Num(); ++FolderIndex)
|
|
{
|
|
FindPakFilesInDirectory(LowLevelFile, *PakFolders[FolderIndex], OutPakFiles);
|
|
}
|
|
}
|
|
|
|
void FPakPlatformFile::GetPakFolders(const TCHAR* CmdLine, TArray<FString>& OutPakFolders)
|
|
{
|
|
#if !UE_BUILD_SHIPPING
|
|
// Command line folders
|
|
FString PakDirs;
|
|
if (FParse::Value(CmdLine, TEXT("-pakdir="), PakDirs))
|
|
{
|
|
TArray<FString> CmdLineFolders;
|
|
PakDirs.ParseIntoArray(CmdLineFolders, TEXT("*"), true);
|
|
OutPakFolders.Append(CmdLineFolders);
|
|
}
|
|
#endif
|
|
|
|
// @todo plugin urgent: Needs to handle plugin Pak directories, too
|
|
// Hardcoded locations
|
|
OutPakFolders.Add(FString::Printf(TEXT("%sPaks/"), *FPaths::GameContentDir()));
|
|
OutPakFolders.Add(FString::Printf(TEXT("%sPaks/"), *FPaths::GameSavedDir()));
|
|
OutPakFolders.Add(FString::Printf(TEXT("%sPaks/"), *FPaths::EngineContentDir()));
|
|
}
|
|
|
|
bool FPakPlatformFile::CheckIfPakFilesExist(IPlatformFile* LowLevelFile, const TArray<FString>& PakFolders)
|
|
{
|
|
TArray<FString> FoundPakFiles;
|
|
FindAllPakFiles(LowLevelFile, PakFolders, FoundPakFiles);
|
|
return FoundPakFiles.Num() > 0;
|
|
}
|
|
|
|
bool FPakPlatformFile::ShouldBeUsed(IPlatformFile* Inner, const TCHAR* CmdLine) const
|
|
{
|
|
#if !USING_SIGNED_CONTENT
|
|
bool Result = FParse::Param(CmdLine, TEXT("Pak")) || FParse::Param(CmdLine, TEXT("Signedpak")) || FParse::Param(CmdLine, TEXT("Signed"));
|
|
if (FPlatformProperties::RequiresCookedData() && !Result && !FParse::Param(CmdLine, TEXT("NoPak")))
|
|
{
|
|
TArray<FString> PakFolders;
|
|
GetPakFolders(CmdLine, PakFolders);
|
|
Result = CheckIfPakFilesExist(Inner, PakFolders);
|
|
}
|
|
return Result;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
bool FPakPlatformFile::Initialize(IPlatformFile* Inner, const TCHAR* CmdLine)
|
|
{
|
|
// Inner is required.
|
|
check(Inner != NULL);
|
|
LowerLevel = Inner;
|
|
|
|
#if !USING_SIGNED_CONTENT
|
|
bSigned = FParse::Param(CmdLine, TEXT("Signedpak")) || FParse::Param(CmdLine, TEXT("Signed"));
|
|
if (!bSigned)
|
|
{
|
|
// Even if -signed is not provided in the command line, use signed reader if the hardcoded key is non-zero.
|
|
FEncryptionKey DecryptionKey;
|
|
DecryptionKey.Exponent.Parse(DECRYPTION_KEY_EXPONENT);
|
|
DecryptionKey.Modulus.Parse(DECRYPTION_KEY_MODULUS);
|
|
bSigned = !DecryptionKey.Exponent.IsZero() && !DecryptionKey.Modulus.IsZero();
|
|
}
|
|
#else
|
|
bSigned = true;
|
|
#endif
|
|
|
|
bool bMountPaks = true;
|
|
TArray<FString> PaksToLoad;
|
|
#if !UE_BUILD_SHIPPING
|
|
// Optionally get a list of pak filenames to load, only these paks will be mounted
|
|
FString CmdLinePaksToLoad;
|
|
if (FParse::Value(CmdLine, TEXT("-paklist="), CmdLinePaksToLoad))
|
|
{
|
|
CmdLinePaksToLoad.ParseIntoArray(PaksToLoad, TEXT("+"), true);
|
|
}
|
|
|
|
//if we are using a fileserver, then dont' mount paks automatically. We only want to read files from the server.
|
|
FString FileHostIP;
|
|
const bool bCookOnTheFly = FParse::Value(FCommandLine::Get(), TEXT("filehostip"), FileHostIP);
|
|
bMountPaks = !bCookOnTheFly;
|
|
#endif
|
|
|
|
if (bMountPaks)
|
|
{
|
|
// Find and mount pak files from the specified directories.
|
|
TArray<FString> PakFolders;
|
|
GetPakFolders(CmdLine, PakFolders);
|
|
TArray<FString> FoundPakFiles;
|
|
FindAllPakFiles(LowerLevel, PakFolders, FoundPakFiles);
|
|
// Sort in descending order.
|
|
FoundPakFiles.Sort(TGreater<FString>());
|
|
// Mount all found pak files
|
|
for (int32 PakFileIndex = 0; PakFileIndex < FoundPakFiles.Num(); PakFileIndex++)
|
|
{
|
|
const FString& PakFilename = FoundPakFiles[PakFileIndex];
|
|
bool bLoadPak = true;
|
|
if (PaksToLoad.Num() && !PaksToLoad.Contains(FPaths::GetBaseFilename(PakFilename)))
|
|
{
|
|
bLoadPak = false;
|
|
}
|
|
if (bLoadPak)
|
|
{
|
|
// hardcode default load ordering of game main pak -> game content -> engine content -> saved dir
|
|
// would be better to make this config but not even the config system is initialized here so we can't do that
|
|
uint32 PakOrder = 0;
|
|
if (PakFilename.StartsWith(FString::Printf(TEXT("%sPaks/%s-"), *FPaths::GameContentDir(), FApp::GetGameName())))
|
|
{
|
|
PakOrder = 4;
|
|
}
|
|
else if (PakFilename.StartsWith(FPaths::GameContentDir()))
|
|
{
|
|
PakOrder = 3;
|
|
}
|
|
else if (PakFilename.StartsWith(FPaths::EngineContentDir()))
|
|
{
|
|
PakOrder = 2;
|
|
}
|
|
else if (PakFilename.StartsWith(FPaths::GameSavedDir()))
|
|
{
|
|
PakOrder = 1;
|
|
}
|
|
|
|
Mount(*PakFilename, PakOrder);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
GPakExec = new FPakExec(*this);
|
|
#endif // !UE_BUILD_SHIPPING
|
|
|
|
FCoreDelegates::OnMountPak.BindRaw(this, &FPakPlatformFile::HandleMountPakDelegate);
|
|
FCoreDelegates::OnUnmountPak.BindRaw(this, &FPakPlatformFile::HandleUnmountPakDelegate);
|
|
#if USE_PAK_PRECACHE
|
|
FPakPrecacher::Init(LowerLevel);
|
|
#endif
|
|
|
|
return !!LowerLevel;
|
|
}
|
|
|
|
bool FPakPlatformFile::Mount(const TCHAR* InPakFilename, uint32 PakOrder, const TCHAR* InPath /*= NULL*/)
|
|
{
|
|
bool bSuccess = false;
|
|
TSharedPtr<IFileHandle> PakHandle = MakeShareable(LowerLevel->OpenRead(InPakFilename));
|
|
if (PakHandle.IsValid())
|
|
{
|
|
FPakFile* Pak = new FPakFile(LowerLevel, InPakFilename, bSigned);
|
|
if (Pak->IsValid())
|
|
{
|
|
if (InPath != NULL)
|
|
{
|
|
Pak->SetMountPoint(InPath);
|
|
}
|
|
FString PakFilename = InPakFilename;
|
|
if ( PakFilename.EndsWith(TEXT("_P.pak")) )
|
|
{
|
|
PakOrder += 100;
|
|
}
|
|
{
|
|
// Add new pak file
|
|
FScopeLock ScopedLock(&PakListCritical);
|
|
FPakListEntry Entry;
|
|
Entry.ReadOrder = PakOrder;
|
|
Entry.PakFile = Pak;
|
|
PakFiles.Add(Entry);
|
|
PakFiles.StableSort();
|
|
}
|
|
bSuccess = true;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogPakFile, Warning, TEXT("Failed to mount pak \"%s\", pak is invalid."), InPakFilename);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogPakFile, Warning, TEXT("Pak \"%s\" does not exist!"), InPakFilename);
|
|
}
|
|
return bSuccess;
|
|
}
|
|
|
|
bool FPakPlatformFile::Unmount(const TCHAR* InPakFilename)
|
|
{
|
|
{
|
|
FScopeLock ScopedLock(&PakListCritical);
|
|
|
|
for (int32 PakIndex = 0; PakIndex < PakFiles.Num(); PakIndex++)
|
|
{
|
|
if (PakFiles[PakIndex].PakFile->GetFilename() == InPakFilename)
|
|
{
|
|
delete PakFiles[PakIndex].PakFile;
|
|
PakFiles.RemoveAt(PakIndex);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
IFileHandle* FPakPlatformFile::CreatePakFileHandle(const TCHAR* Filename, FPakFile* PakFile, const FPakEntry* FileEntry)
|
|
{
|
|
IFileHandle* Result = NULL;
|
|
bool bNeedsDelete = true;
|
|
FArchive* PakReader = PakFile->GetSharedReader(LowerLevel);
|
|
|
|
// Create the handle.
|
|
if (FileEntry->CompressionMethod != COMPRESS_None && PakFile->GetInfo().Version >= FPakInfo::PakFile_Version_CompressionEncryption)
|
|
{
|
|
if (FileEntry->bEncrypted)
|
|
{
|
|
Result = new FPakFileHandle< FPakCompressedReaderPolicy<FPakSimpleEncryption> >(*PakFile, *FileEntry, PakReader, bNeedsDelete);
|
|
}
|
|
else
|
|
{
|
|
Result = new FPakFileHandle< FPakCompressedReaderPolicy<> >(*PakFile, *FileEntry, PakReader, bNeedsDelete);
|
|
}
|
|
}
|
|
else if (FileEntry->bEncrypted)
|
|
{
|
|
Result = new FPakFileHandle< FPakReaderPolicy<FPakSimpleEncryption> >(*PakFile, *FileEntry, PakReader, bNeedsDelete);
|
|
}
|
|
else
|
|
{
|
|
Result = new FPakFileHandle<>(*PakFile, *FileEntry, PakReader, bNeedsDelete);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool FPakPlatformFile::HandleMountPakDelegate(const FString& PakFilePath, uint32 PakOrder, IPlatformFile::FDirectoryVisitor* Visitor)
|
|
{
|
|
bool bReturn = Mount(*PakFilePath, PakOrder);
|
|
if (bReturn && Visitor != nullptr)
|
|
{
|
|
TArray<FPakListEntry> Paks;
|
|
GetMountedPaks(Paks);
|
|
// Find the single pak we just mounted
|
|
for (auto Pak : Paks)
|
|
{
|
|
if (PakFilePath == Pak.PakFile->GetFilename())
|
|
{
|
|
// Get a list of all of the files in the pak
|
|
for (FPakFile::FFileIterator It(*Pak.PakFile); It; ++It)
|
|
{
|
|
Visitor->Visit(*It.Filename(), false);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return bReturn;
|
|
}
|
|
|
|
bool FPakPlatformFile::HandleUnmountPakDelegate(const FString& PakFilePath)
|
|
{
|
|
return Unmount(*PakFilePath);
|
|
}
|
|
|
|
IFileHandle* FPakPlatformFile::OpenRead(const TCHAR* Filename, bool bAllowWrite)
|
|
{
|
|
IFileHandle* Result = NULL;
|
|
FPakFile* PakFile = NULL;
|
|
const FPakEntry* FileEntry = FindFileInPakFiles(Filename, &PakFile);
|
|
if (FileEntry != NULL)
|
|
{
|
|
Result = CreatePakFileHandle(Filename, PakFile, FileEntry);
|
|
}
|
|
#if !USING_SIGNED_CONTENT
|
|
else
|
|
{
|
|
if (IsNonPakFilenameAllowed(Filename))
|
|
{
|
|
// Default to wrapped file
|
|
Result = LowerLevel->OpenRead(Filename, bAllowWrite);
|
|
}
|
|
}
|
|
#endif
|
|
return Result;
|
|
}
|
|
|
|
bool FPakPlatformFile::BufferedCopyFile(IFileHandle& Dest, IFileHandle& Source, const int64 FileSize, uint8* Buffer, const int64 BufferSize) const
|
|
{
|
|
int64 RemainingSizeToCopy = FileSize;
|
|
// Continue copying chunks using the buffer
|
|
while (RemainingSizeToCopy > 0)
|
|
{
|
|
const int64 SizeToCopy = FMath::Min(BufferSize, RemainingSizeToCopy);
|
|
if (Source.Read(Buffer, SizeToCopy) == false)
|
|
{
|
|
return false;
|
|
}
|
|
if (Dest.Write(Buffer, SizeToCopy) == false)
|
|
{
|
|
return false;
|
|
}
|
|
RemainingSizeToCopy -= SizeToCopy;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FPakPlatformFile::CopyFile(const TCHAR* To, const TCHAR* From)
|
|
{
|
|
bool Result = false;
|
|
FPakFile* PakFile = NULL;
|
|
const FPakEntry* FileEntry = FindFileInPakFiles(From, &PakFile);
|
|
if (FileEntry != NULL)
|
|
{
|
|
// Copy from pak to LowerLevel->
|
|
// Create handles both files.
|
|
TAutoPtr<IFileHandle> DestHandle(LowerLevel->OpenWrite(To));
|
|
TAutoPtr<IFileHandle> SourceHandle(CreatePakFileHandle(From, PakFile, FileEntry));
|
|
|
|
if (DestHandle.IsValid() && SourceHandle.IsValid())
|
|
{
|
|
const int64 BufferSize = 64 * 1024; // Copy in 64K chunks.
|
|
uint8* Buffer = (uint8*)FMemory::Malloc(BufferSize);
|
|
Result = BufferedCopyFile(*DestHandle, *SourceHandle, SourceHandle->Size(), Buffer, BufferSize);
|
|
FMemory::Free(Buffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Result = LowerLevel->CopyFile(To, From);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
uint32 ComputePakChunkHash(const uint8* InData, const int64 InDataSize)
|
|
{
|
|
return FCrc::MemCrc32(InData, InDataSize);
|
|
}
|
|
|
|
/**
|
|
* Module for the pak file
|
|
*/
|
|
class FPakFileModule : public IPlatformFileModule
|
|
{
|
|
public:
|
|
virtual IPlatformFile* GetPlatformFile() override
|
|
{
|
|
static TScopedPointer<IPlatformFile> AutoDestroySingleton(new FPakPlatformFile());
|
|
return AutoDestroySingleton.GetOwnedPointer();
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_MODULE(FPakFileModule, PakFile);
|