Files
UnrealEngineUWP/Engine/Source/Runtime/PakFile/Private/IPlatformFilePak.cpp
Andrew Grant 4bf0e40696 Copying //UE4/Orion-Staging to //UE4/Main (Source: //Orion/Dev-General @ 3483207)
#lockdown Nick.Penwarden
#rb na


Change 3483207 on 2017/06/09 by Laurent.Delayen

	Batch Animation Compression fixes.
	- Fixed incorrect 'MemorySavingsFromPrevious' resulting in picking suboptimal compressors.
	- Fixed uncompressed size calculation not taking into account scale component.
	- Fixed animations with 'bDoNotOverrideCompression' causing crashes because they were not recompressed.
	- Animation with 'bDoNotOverrideCompression' that use the automatic compressions are not skipped by the automatic batch compression.
	- Added 'CompressCommandletVersion' to DDC key, so we can force recompression on all animations easily.

	Repopulated DDC with all animations.

	#!codereview martin.wilson
	#!rb lina.halper
	#!tests loaded editor, ran a quick game.

Change 3483107 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Non-shipping changes -
	 Added GPU health check if we are waiting for > 2 secs on the rendering thread
	 Changed param for GPU health checking from aftermath to gpucrashdebugging

	#!tests compiled
	#!rb arne

	#!ROBOMERGE-SOURCE: CL 3483100 in //Orion/Release-40.4/... via CL 3483101 via CL 3483103
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3483106 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Non-shipping changes -
	 Added GPU health check if we are waiting for > 2 secs on the rendering thread
	 Changed param for GPU health checking from aftermath to gpucrashdebugging

	#!tests compiled
	#!rb arne

	#!ROBOMERGE-SOURCE: CL 3483100 in //Orion/Release-40.4/... via CL 3483101 via CL 3483103
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3483105 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Non-shipping changes -
	 Added GPU health check if we are waiting for > 2 secs on the rendering thread
	 Changed param for GPU health checking from aftermath to gpucrashdebugging

	#!tests compiled
	#!rb arne

	#!ROBOMERGE-SOURCE: CL 3483100 in //Orion/Release-40.4/... via CL 3483101 via CL 3483103
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3483104 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Non-shipping changes -
	 Added GPU health check if we are waiting for > 2 secs on the rendering thread
	 Changed param for GPU health checking from aftermath to gpucrashdebugging

	#!tests compiled
	#!rb arne

	#!ROBOMERGE-SOURCE: CL 3483100 in //Orion/Release-40.4/... via CL 3483101 via CL 3483103
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3483103 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Non-shipping changes -
	 Added GPU health check if we are waiting for > 2 secs on the rendering thread
	 Changed param for GPU health checking from aftermath to gpucrashdebugging

	#!tests compiled
	#!rb arne

	#!ROBOMERGE-SOURCE: CL 3483100 in //Orion/Release-40.4/... via CL 3483101
	#!ROBOMERGE-BOT: ORION (Release-40.5 -> Main)

Change 3483101 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Non-shipping changes -
	 Added GPU health check if we are waiting for > 2 secs on the rendering thread
	 Changed param for GPU health checking from aftermath to gpucrashdebugging

	#!tests compiled
	#!rb arne

	#!ROBOMERGE-SOURCE: CL 3483100 in //Orion/Release-40.4/...
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Release-40.5)

Change 3483100 on 2017/06/09 by Andrew.Grant

	Non-shipping changes -
	 Added GPU health check if we are waiting for > 2 secs on the rendering thread
	 Changed param for GPU health checking from aftermath to gpucrashdebugging

	#!tests compiled
	#!rb arne

Change 3482985 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Fixed up the allocated small pool memory stat.
	#!rb Andrew.Grant
	#!test Paragon startup
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3482444 in //Orion/Release-40.4/... via CL 3482448 via CL 3482449
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3482984 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Fixed up the allocated small pool memory stat.
	#!rb Andrew.Grant
	#!test Paragon startup
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3482444 in //Orion/Release-40.4/... via CL 3482448 via CL 3482449
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3482983 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Fixed up the allocated small pool memory stat.
	#!rb Andrew.Grant
	#!test Paragon startup
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3482444 in //Orion/Release-40.4/... via CL 3482448 via CL 3482449
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3482982 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Fixed up the allocated small pool memory stat.
	#!rb Andrew.Grant
	#!test Paragon startup
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3482444 in //Orion/Release-40.4/... via CL 3482448 via CL 3482449
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3482981 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Fixed up the allocated small pool memory stat.
	#!rb Andrew.Grant
	#!test Paragon startup
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3482444 in //Orion/Release-40.4/... via CL 3482448 via CL 3482449
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3482612 on 2017/06/09 by Frank.Fella

	Niagara - Fix various wiring issues.
	+ Reverting dynamic inputs no longer leaves the graph disconnected.
	+ Reverting dynamic inputs no longer leaves the controls in the stack.
	+ Adding multiple dynamic inputs to the same module now wires them correctly.
	+ Adding dynamic inputs when there is already an override read now wires correctly.
	+ Moving modules with dynamic inputs up and down and removing them now works correctly.

	#!tests Everything above.
	#!rb none

	#!codereview Olaf.Piesche,Simon.Tovey,Shaun.Kime

Change 3482449 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Fixed up the allocated small pool memory stat.
	#!rb Andrew.Grant
	#!test Paragon startup
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3482444 in //Orion/Release-40.4/... via CL 3482448
	#!ROBOMERGE-BOT: ORION (Release-40.5 -> Main)

Change 3482448 on 2017/06/09 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Fixed up the allocated small pool memory stat.
	#!rb Andrew.Grant
	#!test Paragon startup
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3482444 in //Orion/Release-40.4/...
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Release-40.5)

Change 3482444 on 2017/06/09 by Daniel.Lamb

	Fixed up the allocated small pool memory stat.
	#!rb Andrew.Grant
	#!test Paragon startup
	#!lockdown Andrew.Grant

Change 3482261 on 2017/06/09 by Shaun.Kime

	Made Get/Set nodes available at all times.
	Tweaked the right-click menu on parameter map base to allow for particle namespaced custom variables and also limiting based on script context.

	#!rb none
	#!tests n/a

Change 3482147 on 2017/06/09 by Shaun.Kime

	Fixing crash when updating the vertex data and the vertex attributes are no longer part of the data set.

	#!rb none
	#!tests opened existing files

Change 3482076 on 2017/06/09 by Wyeth.Johnson

	Resave to prevent the constant recompiling of DefaultParticle

Change 3481302 on 2017/06/08 by Shaun.Kime

	Adding a FunctionCall derived node type that allows you to set any namespaced pin by name and type.

	#!rb none
	#!tests created emitter with values in spawn and update
	#!codereview frank.fella

Change 3480830 on 2017/06/08 by Laurent.Delayen

	First batch of recompressed animations.

	#!codereview jay.hosfelt, dwayne.martin
	#!lockdown Andrew.Bains

Change 3480524 on 2017/06/08 by Laurent.Delayen

	Fixed CompressAnimations Commandlet to work with new DDC refactor.

	#!codereview martin.wilson
	#!rb lina.halper
	#!tests Paragon full animation recompression.

Change 3480278 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Additional logging for OR-38938
	#!rb Ryan.Gerleve
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3479906 in //Orion/Release-40.4/... via CL 3479909 via CL 3479910
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3480277 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Additional logging for OR-38938
	#!rb Ryan.Gerleve
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3479906 in //Orion/Release-40.4/... via CL 3479909 via CL 3479910
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3480276 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Additional logging for OR-38938
	#!rb Ryan.Gerleve
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3479906 in //Orion/Release-40.4/... via CL 3479909 via CL 3479910
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3480273 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Additional logging for OR-38938
	#!rb Ryan.Gerleve
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3479906 in //Orion/Release-40.4/... via CL 3479909 via CL 3479910
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3480270 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Additional logging for OR-38938
	#!rb Ryan.Gerleve
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3479906 in //Orion/Release-40.4/... via CL 3479909 via CL 3479910
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3480090 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked 40.3 builds to 3472726
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3479202 in //Orion/Release-40.3/... via CL 3479203 via CL 3479204 via CL 3479205
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3480089 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked 40.3 builds to 3472726
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3479202 in //Orion/Release-40.3/... via CL 3479203 via CL 3479204 via CL 3479205
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3480088 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked 40.3 builds to 3472726
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3479202 in //Orion/Release-40.3/... via CL 3479203 via CL 3479204 via CL 3479205
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3480087 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked 40.3 builds to 3472726
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3479202 in //Orion/Release-40.3/... via CL 3479203 via CL 3479204 via CL 3479205
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3480086 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked 40.3 builds to 3472726
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3479202 in //Orion/Release-40.3/... via CL 3479203 via CL 3479204 via CL 3479205
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3480085 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Added stats to MallocBinned2.
	#!rb Andrew.Grant
	#!test Paragon PS4
	[CODEREVIEW] Gil.Gribb
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3479159 in //Orion/Release-40.4/... via CL 3479160 via CL 3479161
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3480084 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Added stats to MallocBinned2.
	#!rb Andrew.Grant
	#!test Paragon PS4
	[CODEREVIEW] Gil.Gribb
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3479159 in //Orion/Release-40.4/... via CL 3479160 via CL 3479161
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3480083 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Added stats to MallocBinned2.
	#!rb Andrew.Grant
	#!test Paragon PS4
	[CODEREVIEW] Gil.Gribb
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3479159 in //Orion/Release-40.4/... via CL 3479160 via CL 3479161
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3480082 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Added stats to MallocBinned2.
	#!rb Andrew.Grant
	#!test Paragon PS4
	[CODEREVIEW] Gil.Gribb
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3479159 in //Orion/Release-40.4/... via CL 3479160 via CL 3479161
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3480081 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Added stats to MallocBinned2.
	#!rb Andrew.Grant
	#!test Paragon PS4
	[CODEREVIEW] Gil.Gribb
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3479159 in //Orion/Release-40.4/... via CL 3479160 via CL 3479161
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3480073 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: jeff.williams
	Removing implicit requirements to display Badges

	Badge requirements are not considered while culling nodes from the build graph. This allowed implicit dependencies resolved before culling to invalidate badges afterwards. Only explicitly declared dependencies are now used to validate badges.

	#!rb none
	#!tests compile, validated export output

	#!ROBOMERGE-SOURCE: CL 3479012 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3480072 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: jeff.williams
	Removing implicit requirements to display Badges

	Badge requirements are not considered while culling nodes from the build graph. This allowed implicit dependencies resolved before culling to invalidate badges afterwards. Only explicitly declared dependencies are now used to validate badges.

	#!rb none
	#!tests compile, validated export output

	#!ROBOMERGE-SOURCE: CL 3479012 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3480071 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: jeff.williams
	Removing implicit requirements to display Badges

	Badge requirements are not considered while culling nodes from the build graph. This allowed implicit dependencies resolved before culling to invalidate badges afterwards. Only explicitly declared dependencies are now used to validate badges.

	#!rb none
	#!tests compile, validated export output

	#!ROBOMERGE-SOURCE: CL 3479012 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3480070 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: jeff.williams
	Removing implicit requirements to display Badges

	Badge requirements are not considered while culling nodes from the build graph. This allowed implicit dependencies resolved before culling to invalidate badges afterwards. Only explicitly declared dependencies are now used to validate badges.

	#!rb none
	#!tests compile, validated export output

	#!ROBOMERGE-SOURCE: CL 3479012 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3480069 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: jeff.williams
	Removing implicit requirements to display Badges

	Badge requirements are not considered while culling nodes from the build graph. This allowed implicit dependencies resolved before culling to invalidate badges afterwards. Only explicitly declared dependencies are now used to validate badges.

	#!rb none
	#!tests compile, validated export output

	#!ROBOMERGE-SOURCE: CL 3479012 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3479910 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Additional logging for OR-38938
	#!rb Ryan.Gerleve
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3479906 in //Orion/Release-40.4/... via CL 3479909
	#!ROBOMERGE-BOT: ORION (Release-40.5 -> Main)

Change 3479909 on 2017/06/08 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Additional logging for OR-38938
	#!rb Ryan.Gerleve
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3479906 in //Orion/Release-40.4/...
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Release-40.5)

Change 3479906 on 2017/06/08 by Andrew.Grant

	Additional logging for OR-38938
	#!rb Ryan.Gerleve
	#!tests compiled

Change 3479800 on 2017/06/08 by Dan.Hertzka

	EditCondition UProperty metadata works on UStruct properties as well (including data table row structs)
	- Submitting on behalf of Jamie Dale (thanks Jamie!)

	#!rb Jamie.Dale
	#!tests EditCondition works for both UClass and UStruct properties

Change 3479765 on 2017/06/08 by Simon.Tovey

	Allow overriding of collections per component from BP and a functional test map for it.

	#!rb none
	#!tests test map works
	#!codereview Olaf.Piesche, Frank.Fella, Shaun.Kime

Change 3479205 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked 40.3 builds to 3472726
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3479202 in //Orion/Release-40.3/... via CL 3479203 via CL 3479204
	#!ROBOMERGE-BOT: ORION (Release-40.5 -> Main)

Change 3479204 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked 40.3 builds to 3472726
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3479202 in //Orion/Release-40.3/... via CL 3479203
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Release-40.5)

Change 3479203 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked 40.3 builds to 3472726
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3479202 in //Orion/Release-40.3/...
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Release-40.4)

Change 3479202 on 2017/06/07 by Andrew.Grant

	Locked 40.3 builds to 3472726
	#!ROBOMERGE: !40.4
	#!tests #!rb none

Change 3479161 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Added stats to MallocBinned2.
	#!rb Andrew.Grant
	#!test Paragon PS4
	[CODEREVIEW] Gil.Gribb
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3479159 in //Orion/Release-40.4/... via CL 3479160
	#!ROBOMERGE-BOT: ORION (Release-40.5 -> Main)

Change 3479160 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: daniel.lamb
	Added stats to MallocBinned2.
	#!rb Andrew.Grant
	#!test Paragon PS4
	[CODEREVIEW] Gil.Gribb
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3479159 in //Orion/Release-40.4/...
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Release-40.5)

Change 3479159 on 2017/06/07 by Daniel.Lamb

	Added stats to MallocBinned2.
	#!rb Andrew.Grant
	#!test Paragon PS4
	#!codereview Gil.Gribb
	#!lockdown Andrew.Grant

Change 3479012 on 2017/06/07 by Jeff.Williams

	Removing implicit requirements to display Badges

	Badge requirements are not considered while culling nodes from the build graph. This allowed implicit dependencies resolved before culling to invalidate badges afterwards. Only explicitly declared dependencies are now used to validate badges.

	#!rb none
	#!tests compile, validated export output

Change 3478991 on 2017/06/07 by Shaun.Kime

	Added auto-compile to emitters. It is an emitter-wide value, toggled by the dropdown next to the compile button.

	#!rb none
	#!tests made multiple edits to an emitter

Change 3478976 on 2017/06/07 by Max.Chen

	Sequencer: Fix burnin when there are warmup frames. The current time used for the burnin is offset from the playback range's start time. When using warmup frames, the start time will include the warmup time so it needs to be factored out when setting the actual current time for the frame.

	#!jira UE-45737
	#!rb none
	#!codereview andrew.rodham
	#!tests none

Change 3478426 on 2017/06/07 by David.Ratti

	Expose some ability system stuff to blueprints:
	-Query for AGE Handle based on GE Query
	-Methods for accessing AGE start/end/duration values

	Test asset for bill for example

	#!rb none
	#!tests pie
	#!review-3478427 Jon.Lietz, @John.Nielson

Change 3478424 on 2017/06/07 by Laurent.Delayen

	Prevent creating invalid 'VBCompactPoseData', resulting in crashes in Animation Modifiers.
	(Fix for licensee crash).

	#!rb lina.halper
	#!codereview martin.wilson
	#!tests Ice sync marker automator from Athomas.

Change 3478151 on 2017/06/07 by David.Ratti

	spot edigrate GameplayTagQuery customization fix for crash when editing query on bp defaults.
	#!rb none
	#!tests compile

Change 3477983 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: alexis.matte
	[NULL MERGE]
	Fix morph target import

	#!jira OR-38471
	#!rb none
	#!tests none
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3477453 in //Orion/Release-40.4/... via CL 3477925 via CL 3477941
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3477982 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: alexis.matte
	[NULL MERGE]
	Fix morph target import

	#!jira OR-38471
	#!rb none
	#!tests none
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3477453 in //Orion/Release-40.4/... via CL 3477925 via CL 3477941
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3477981 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: alexis.matte
	[NULL MERGE]
	Fix morph target import

	#!jira OR-38471
	#!rb none
	#!tests none
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3477453 in //Orion/Release-40.4/... via CL 3477925 via CL 3477941
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3477980 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: alexis.matte
	[NULL MERGE]
	Fix morph target import

	#!jira OR-38471
	#!rb none
	#!tests none
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3477453 in //Orion/Release-40.4/... via CL 3477925 via CL 3477941
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3477979 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: alexis.matte
	[NULL MERGE]
	Fix morph target import

	#!jira OR-38471
	#!rb none
	#!tests none
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3477453 in //Orion/Release-40.4/... via CL 3477925 via CL 3477941
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3477941 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: alexis.matte
	[NULL MERGE]
	Fix morph target import

	#!jira OR-38471
	#!rb none
	#!tests none
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3477453 in //Orion/Release-40.4/... via CL 3477925
	#!ROBOMERGE-BOT: ORION (Release-40.5 -> Main)

Change 3477925 on 2017/06/07 by robomerge

	#!ROBOMERGE-AUTHOR: alexis.matte
	Fix morph target import

	#!jira OR-38471
	#!rb none
	#!tests none
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3477453 in //Orion/Release-40.4/...
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Release-40.5)
	#!ROBOMERGE[ORION]: !Main

Change 3477774 on 2017/06/07 by Alexis.Matte

	implement a dev-editor cl 3470188
	Fix the material isolate for cloth or hair
	#!jira UE-38985
	#!rb none
	#!tests none

Change 3477722 on 2017/06/07 by Don.Eubanks

	Re-enabling D-Pad navigation support in card shop.

	Exposed OnNavigation to UserWidget in the form of NativeOnNavigation, leveraged this new feature to have the classes I care about (HandEntry / CardShopEquipSlot)

	Split out BaseButton_Group's "SelectNextButton" process into "GetButton" and "Select Button" so I could use the GetButton when doing navigation.

	#!rb matt.schembari
	#!tests Compile DebugGameEditor Win64 / Shipping Client PS4

Change 3477610 on 2017/06/07 by Shaun.Kime

	Fixing up emitter nodes in system graph when deleted

	#!rb none
	#!tests added/removed multiple emitters

Change 3477528 on 2017/06/07 by Simon.Tovey

	? Fixed up issue with interface function binding from the removal of variable IDs.
	? Fixed issue where system parameters were garbage on the first tick of a system.
	? Bypassed issue with component lifetime. When destroying systems in some cases the component is pending kill so it's weak ptr returns null.
	We need to investigate this further.

	#!rb none
	#!tests stuff works
	#!codereview Olaf.Piesche, Frank.Fella, Shaun.Kime

Change 3477453 on 2017/06/07 by Alexis.Matte

	Fix morph target import

	#!jira OR-38471
	#!rb none
	#!tests none
	#!ROBOMERGE: !Main
	#!lockdown Andrew.Grant

Change 3477182 on 2017/06/07 by Frank.Fella

	Niagara - Rename files from class renames in last check-in.

	#!tests Compiled.
	#!rb none

Change 3477171 on 2017/06/06 by Frank.Fella

	Niagara - Can now add dynamic inputs directly in the stack.

	#!tests Added dynamic inputs directly from the stack.
	#!rb none

	#!codereview Olaf.Piesche,Simon.Tovey,Shaun.Kime

Change 3477115 on 2017/06/06 by Jeff.Williams

	Merging //Orion/Main to Release-40.5 (//Orion/Release-40.5) @3477068

	#!rb none
	#!tests none

Change 3477098 on 2017/06/06 by Jeff.Williams

	Initial branch of files from Release-40.4 (//Orion/Release-40.4) to Release-40.5 (//Orion/Release-40.5)

Change 3476585 on 2017/06/06 by Mieszko.Zielinski

	EQS touches to hopefully address the elusive EQS NaN in live build #!Orion

	#!test golden path
	#!rb none

Change 3476342 on 2017/06/06 by Laurent.Delayen

	FCSPose<PoseType>::ConvertToLocalPoses Allow root bone to be modified. Minor optimization: Take out root bone check from loop.

	#!rb lina.halper
	#!tests Ghost PIE

Change 3476336 on 2017/06/06 by Shaun.Kime

	First pass at trying to prevent Wyeth's crash in the EmitterInstance destructor.

	#!rb none
	#!tests tried iterating with multiple changes between emitters/systems
	#!codereview simon.tovey, frank.fella, olaf.piesche

Change 3476160 on 2017/06/06 by Shaun.Kime

	Removing ID's from FNiagaraVariables. Reworking existing code to properly handle this.

	#!rb none
	#!codereview simon.tovey, frank.fella, olaf.piesche
	#!tests recompiled and ran existing emitters, created system, iterated between system and emitter

Change 3476157 on 2017/06/06 by Shaun.Kime

	Fixing code dependency

	#!rb none
	#!tests n/a

Change 3476155 on 2017/06/06 by Shaun.Kime

	Added ability to get Emitter alias from parameter map

	#!tests n/a
	#!rb none

Change 3476152 on 2017/06/06 by Shaun.Kime

	Fixing comment so that system tooltip was meaningful from creation menu

	#!rb none
	#!tests  n/a

Change 3476148 on 2017/06/06 by Shaun.Kime

	Removing gamethread checks as we use a parallel for to update emitter instances, causing this to always fail with multiple emitters in a system.

	#!rb none
	#!codereview simon.tovey, olaf.piesche
	#!tests added multiple emitters and didn't crash

Change 3475898 on 2017/06/06 by Mieszko.Zielinski

	Manual recreation of CL#!3465092 #!UE4

	By LukaszF: "fixed navigation area modifiers created from shape components: sphere and capsule"

	#!test golden path
	#!rb none

Change 3475817 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Now with correctly unshelved CL - made Aftermath a command line option

	#!tests compiled, verified initialziation is command line driven
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3475810 in //Orion/Release-40.4/... via CL 3475812
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3475816 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Now with correctly unshelved CL - made Aftermath a command line option

	#!tests compiled, verified initialziation is command line driven
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3475810 in //Orion/Release-40.4/... via CL 3475812
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3475815 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Now with correctly unshelved CL - made Aftermath a command line option

	#!tests compiled, verified initialziation is command line driven
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3475810 in //Orion/Release-40.4/... via CL 3475812
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3475814 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Now with correctly unshelved CL - made Aftermath a command line option

	#!tests compiled, verified initialziation is command line driven
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3475810 in //Orion/Release-40.4/... via CL 3475812
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3475813 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Now with correctly unshelved CL - made Aftermath a command line option

	#!tests compiled, verified initialziation is command line driven
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3475810 in //Orion/Release-40.4/... via CL 3475812
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3475812 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Now with correctly unshelved CL - made Aftermath a command line option

	#!tests compiled, verified initialziation is command line driven
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3475810 in //Orion/Release-40.4/...
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Main)

Change 3475810 on 2017/06/06 by Andrew.Grant

	Now with correctly unshelved CL - made Aftermath a command line option

	#!tests compiled, verified initialziation is command line driven
	#!rb none

Change 3475792 on 2017/06/06 by Jon.Lietz

	item cooldowns

	- added in native ability class (UOrionSourceItemAbility) that will be repsonsible for item keyword cooldowns and cost.
	- Moved Application, trigger and activation/deactivation of itemkeywords out of the deck instance and into UOrionSourceItemAbility.
	- added in support for cultivate card trait
	- added in to the engine FAbilityEndedData that will pass through delegates what ability ended the spec handle and if it was cancelled or not
	- added 2 delegates for when abilities end, one inside UAbilitySystemComponent::NotifyAbilityEnded() the other in UGameplayAbility::EndAbility() they bost pass through a const  FAbilityEndedData&

	#!rb david.ratti
	#!tests buy and play cards

Change 3475760 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made aftermath iniitialization off by default and controlled by the -aftermath command line option
	Logs are now warnings if aftermath is requested but can't be initialized

	#!tests verified command line test works
	#!rb none
	@marcus.wassmer, @arne.schober

	#!ROBOMERGE-SOURCE: CL 3475753 in //Orion/Release-40.4/... via CL 3475755
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3475759 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made aftermath iniitialization off by default and controlled by the -aftermath command line option
	Logs are now warnings if aftermath is requested but can't be initialized

	#!tests verified command line test works
	#!rb none
	@marcus.wassmer, @arne.schober

	#!ROBOMERGE-SOURCE: CL 3475753 in //Orion/Release-40.4/... via CL 3475755
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3475758 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made aftermath iniitialization off by default and controlled by the -aftermath command line option
	Logs are now warnings if aftermath is requested but can't be initialized

	#!tests verified command line test works
	#!rb none
	@marcus.wassmer, @arne.schober

	#!ROBOMERGE-SOURCE: CL 3475753 in //Orion/Release-40.4/... via CL 3475755
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3475757 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made aftermath iniitialization off by default and controlled by the -aftermath command line option
	Logs are now warnings if aftermath is requested but can't be initialized

	#!tests verified command line test works
	#!rb none
	@marcus.wassmer, @arne.schober

	#!ROBOMERGE-SOURCE: CL 3475753 in //Orion/Release-40.4/... via CL 3475755
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3475756 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made aftermath iniitialization off by default and controlled by the -aftermath command line option
	Logs are now warnings if aftermath is requested but can't be initialized

	#!tests verified command line test works
	#!rb none
	@marcus.wassmer, @arne.schober

	#!ROBOMERGE-SOURCE: CL 3475753 in //Orion/Release-40.4/... via CL 3475755
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3475755 on 2017/06/06 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made aftermath iniitialization off by default and controlled by the -aftermath command line option
	Logs are now warnings if aftermath is requested but can't be initialized

	#!tests verified command line test works
	#!rb none
	@marcus.wassmer, @arne.schober

	#!ROBOMERGE-SOURCE: CL 3475753 in //Orion/Release-40.4/...
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Main)

Change 3475753 on 2017/06/06 by Andrew.Grant

	Made aftermath iniitialization off by default and controlled by the -aftermath command line option
	Logs are now warnings if aftermath is requested but can't be initialized

	#!tests verified command line test works
	#!rb none
	#!review-3475754 @marcus.wassmer, @arne.schober

Change 3475491 on 2017/06/06 by Simon.Tovey

	Feeding parameter collection values into simulaitons.

	? Setup binding from parameter collections to simulation exec contexts. Data is fed in now.
	? Modified names of collection parameter such that they're always uniquely associated with a particular collection. In case two sets use the same name for example.
	Required some name conversion between the internals and the UI.
	? Modified node to not link to params by ID as they will be removed shortly.
	? NiagaraWorldManager now ticking to push parameter data from global collections.
	? Added BP function library call to grab the global collection instance for a collection and BP getters and setters for instances.
	? Components also can override the global instance though this isn't hooked up to anything as yet. I imagine this will be handy for creating override volumes in the world and having components interpolate between those similar to post process volumes.

	Minor/unrelated
	? Fixed crash on exit. Changed system instance in component to be Unique ptr and always access via component to more direcly control lifetime.
	? Crash fix when getting matrices from parameter map. TypeEditorUtilities was null.
	? Fixed bug in GetTypeDefaultValue()
	? Fixed property tagging on FNiagaraStatScope

	#!tests emitters work. Data is fed in.
	#!rb none
	#!codereview Olaf.Piesche, Shaun.Kime, Frank.Fella

Change 3474483 on 2017/06/05 by Laurent.Delayen

	Added new BlendBoneByChannel AnimNode to blend two poses, per bone, per channel. For example blend only translation from Pelvis.

	#!rb none
	#!test Ghost
	#!codereview lina.halper

Change 3474099 on 2017/06/05 by Alexis.Matte

	Copy/paste material should copy paste only the material instance
	#!rb none
	#!test none

Change 3474073 on 2017/06/05 by Daniel.Lamb

	Added estimated timing for reatltime updates.
	#!rb Trivial
	#!test Launch build paragon.

Change 3474066 on 2017/06/05 by Daniel.Lamb

	Increased heartbeat frequency for realtime cooking.
	#!rb Trivial
	#!test Realtime cooking

Change 3473623 on 2017/06/05 by Daniel.Lamb

	Using notimeouts on client and server when running realtime cooking, as the client is slowed down making it timeout.
	#!rb Trivial
	#!test Realtime cook paragon orion_entry.

Change 3473484 on 2017/06/05 by Frank.Fella

	Niagara - Preliminary support for dynamic inputs.

	#!tests Dynamic inputs are shown in the stack UI and their inputs are editable.
	#!rb none

	#!codereview Olaf.Piesche,Simon.Tovey,Shaun.Kime

Change 3473481 on 2017/06/05 by Frank.Fella

	Niagara - Highlight the connecting wire when hovering the wire itself or one of it's connected pins.

	#!tests The wire highlights.
	#!rb none

	#!codereview Olaf.Piesche,Simon.Tovey,Shaun.Kime

Change 3473480 on 2017/06/05 by Frank.Fella

	Niagara - Notify the graph that it has changed when adding and connecting pins on a node with dynamic pins.

	#!tests The graph is now shown as modified and needing compiling when connecting or adding pins on a node with dynamic pins.

	#!rb none
	#!codereview Olaf.Piesche,Simon.Tovey,Shaun.Kime

Change 3473479 on 2017/06/05 by Frank.Fella

	Niagara - Fix an issue where module inputs were not getting aliased correctly when there was more than one of the same node when modifying them from the stack.

	#!test The inputs now get aliased correctly.
	#!rb none

	#!codereview Olaf.Piesche,Simon.Tovey,Shaun.Kime

Change 3472889 on 2017/06/03 by Andrew.Grant

	Fixed merge error
	#!tests compiled
	#!rb none

Change 3472547 on 2017/06/02 by Olaf.Piesche

	Use the correct number of instances after sim step; this makes killing particles work properly in GPU sim

	#!codereview simon.tovey
	#!rb none
	#!tests GPUTest emitter and OrbitalMotion test emitter

Change 3472452 on 2017/06/02 by Olaf.Piesche

	More GPU spawn fixes; no more garbage particles in buffers after spawning with GPU simulation
	Bit more cleanup

	#!rb none
	#!tests GPUTest emitter
	#!codereview simon.tovey

Change 3472284 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - if the specified build has a client but not a server, fallback to using the editor as a server

	@Daniel.Lamb
	#!tests ran Gauntlet on build with / without server
	#!rb -

	#!ROBOMERGE-SOURCE: CL 3471727 in //Orion/Release-40.3/... via CL 3472202 via CL 3472213
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3472283 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - if the specified build has a client but not a server, fallback to using the editor as a server

	@Daniel.Lamb
	#!tests ran Gauntlet on build with / without server
	#!rb -

	#!ROBOMERGE-SOURCE: CL 3471727 in //Orion/Release-40.3/... via CL 3472202 via CL 3472213
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3472282 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - if the specified build has a client but not a server, fallback to using the editor as a server

	@Daniel.Lamb
	#!tests ran Gauntlet on build with / without server
	#!rb -

	#!ROBOMERGE-SOURCE: CL 3471727 in //Orion/Release-40.3/... via CL 3472202 via CL 3472213
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3472278 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - if the specified build has a client but not a server, fallback to using the editor as a server

	@Daniel.Lamb
	#!tests ran Gauntlet on build with / without server
	#!rb -

	#!ROBOMERGE-SOURCE: CL 3471727 in //Orion/Release-40.3/... via CL 3472202 via CL 3472213
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3472275 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - if the specified build has a client but not a server, fallback to using the editor as a server

	@Daniel.Lamb
	#!tests ran Gauntlet on build with / without server
	#!rb -

	#!ROBOMERGE-SOURCE: CL 3471727 in //Orion/Release-40.3/... via CL 3472202 via CL 3472213
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3472213 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - if the specified build has a client but not a server, fallback to using the editor as a server

	@Daniel.Lamb
	#!tests ran Gauntlet on build with / without server
	#!rb -

	#!ROBOMERGE-SOURCE: CL 3471727 in //Orion/Release-40.3/... via CL 3472202
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Main)

Change 3472202 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - if the specified build has a client but not a server, fallback to using the editor as a server

	@Daniel.Lamb
	#!tests ran Gauntlet on build with / without server
	#!rb -

	#!ROBOMERGE-SOURCE: CL 3471727 in //Orion/Release-40.3/...
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Release-40.4)

Change 3471976 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	Gauntlet script fixes
	#!tests ran locally
	#!rb AG

	#!ROBOMERGE-SOURCE: CL 3471604 in //Orion/Release-40.4/... via CL 3471809
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3471975 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	Gauntlet script fixes
	#!tests ran locally
	#!rb AG

	#!ROBOMERGE-SOURCE: CL 3471604 in //Orion/Release-40.4/... via CL 3471809
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3471974 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	Gauntlet script fixes
	#!tests ran locally
	#!rb AG

	#!ROBOMERGE-SOURCE: CL 3471604 in //Orion/Release-40.4/... via CL 3471809
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3471973 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	Gauntlet script fixes
	#!tests ran locally
	#!rb AG

	#!ROBOMERGE-SOURCE: CL 3471604 in //Orion/Release-40.4/... via CL 3471809
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3471972 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	Gauntlet script fixes
	#!tests ran locally
	#!rb AG

	#!ROBOMERGE-SOURCE: CL 3471604 in //Orion/Release-40.4/... via CL 3471809
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3471966 on 2017/06/02 by Andrew.Grant

	Fixed robomerge integration
	#!tests #!rb none

Change 3471845 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	AG - made local builds use editor server
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471566 in //Orion/Release-40.4/... via CL 3471806
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3471844 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	AG - made local builds use editor server
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471566 in //Orion/Release-40.4/... via CL 3471806
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3471843 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	AG - made local builds use editor server
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471566 in //Orion/Release-40.4/... via CL 3471806
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3471842 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	AG - made local builds use editor server
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471566 in //Orion/Release-40.4/... via CL 3471806
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3471835 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: ben.marsh
	Remove setting to copy full crash dumps to \\epicgames.net\root\Projects\Paragon\QA_CrashReports. Don't think anyone is using this.

	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471379 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3471834 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: ben.marsh
	Remove setting to copy full crash dumps to \\epicgames.net\root\Projects\Paragon\QA_CrashReports. Don't think anyone is using this.

	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471379 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3471833 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: ben.marsh
	Remove setting to copy full crash dumps to \\epicgames.net\root\Projects\Paragon\QA_CrashReports. Don't think anyone is using this.

	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471379 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3471832 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: ben.marsh
	Remove setting to copy full crash dumps to \\epicgames.net\root\Projects\Paragon\QA_CrashReports. Don't think anyone is using this.

	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471379 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3471831 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: ben.marsh
	Remove setting to copy full crash dumps to \\epicgames.net\root\Projects\Paragon\QA_CrashReports. Don't think anyone is using this.

	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471379 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3471809 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	Gauntlet script fixes
	#!tests ran locally
	#!rb AG

	#!ROBOMERGE-SOURCE: CL 3471604 in //Orion/Release-40.4/...
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Main)

Change 3471806 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: nick.reid
	AG - made local builds use editor server
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3471566 in //Orion/Release-40.4/...
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Main)

Change 3471727 on 2017/06/02 by Andrew.Grant

	Gauntlet - if the specified build has a client but not a server, fallback to using the editor as a server

	#!review-3471728 @Daniel.Lamb
	#!tests ran Gauntlet on build with / without server
	#!rb -

Change 3471689 on 2017/06/02 by Zak.Middleton

	#!ue4-orion - Added virtual OnClientCorrectionReceived() to CharacterMovement.

	Stubbed implementation for Orion to be replaced/augmented for analytics.

	#!codereview Andrew.Grant
	#!rb none
	#!jira OR-37131
	#!tests Multi PIE

Change 3471654 on 2017/06/02 by Andrew.Grant

	Merging file cull from //Orion/Main to Dev-Balance (//Orion/Dev-Balance)
	#!tests #!rb na

Change 3471627 on 2017/06/02 by Andrew.Grant

	Merging file pruning from //Orion/Main to Dev-Cinematics (//Orion/Dev-Cinematics)
	#!tests #!rb na

Change 3471604 on 2017/06/02 by Nick.Reid

	Gauntlet script fixes
	#!tests ran locally
	#!rb AG

Change 3471566 on 2017/06/02 by Nick.Reid

	AG - made local builds use editor server
	#!tests ran locally
	#!rb none

Change 3471379 on 2017/06/02 by Ben.Marsh

	Remove setting to copy full crash dumps to \\epicgames.net\root\Projects\Paragon\QA_CrashReports. Don't think anyone is using this.

	#!rb none

Change 3471304 on 2017/06/02 by andrew.grant

	Removing some unused files to free up space across branches
	#!tests compiled locally, preflighted standard build
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3470976 in //Orion/Release-40.2/... via CL 3471002 via CL 3471024 via CL 3471072
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

	#!ROBOMERGE-SAYS: Unresolved conflicts. andrew.grant, please merge this change by hand.
	//ROBOMERGE_ORION_Dev_General/Engine/Source/ThirdParty/PhysX/Lib/Win32/VS2015/APEX_Clothing_x86.lib
	//ROBOMERGE_ORION_Dev_General/Engine/Source/ThirdParty/PhysX/Lib/Win32/VS2015/APEX_ClothingCHECKED_x86.lib
	//ROBOMERGE_ORION_Dev_General/Engine/Source/ThirdParty/PhysX/Lib/Win32/VS2015/APEX_ClothingPROFILE_x86.lib
	//ROBOMERGE_ORION_Dev_General/Engine/Source/ThirdParty/PhysX/Lib/Win32/VS2015/APEX_Destructible_x86.lib
	//ROBOMERGE_ORION_Dev_General/Engine/Source/ThirdParty/PhysX/Lib/Win32/VS2015/APEX_DestructibleCHECKED_x86.lib
	//ROBOMERGE_ORION_Dev_General/Engine/Source/ThirdParty/PhysX/Lib/Win32/VS2015/APEX_DestructiblePROFILE_x86.lib
	//ROBOMERGE_ORION_Dev_General/Engine/Source/ThirdParty/PhysX...
	#!CodeReview: andrew.grant, jason.bestimt, jeff.williams

Change 3471231 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removing some unused files to free up space across branches
	#!tests compiled locally, preflighted standard build
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3470976 in //Orion/Release-40.2/... via CL 3471002 via CL 3471024 via CL 3471072
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3471205 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removing some unused files to free up space across branches
	#!tests compiled locally, preflighted standard build
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3470976 in //Orion/Release-40.2/... via CL 3471002 via CL 3471024 via CL 3471072
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3471072 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removing some unused files to free up space across branches
	#!tests compiled locally, preflighted standard build
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3470976 in //Orion/Release-40.2/... via CL 3471002 via CL 3471024
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Main)

Change 3471024 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removing some unused files to free up space across branches
	#!tests compiled locally, preflighted standard build
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3470976 in //Orion/Release-40.2/... via CL 3471002
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Release-40.4)

Change 3471002 on 2017/06/02 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removing some unused files to free up space across branches
	#!tests compiled locally, preflighted standard build
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3470976 in //Orion/Release-40.2/...
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3470976 on 2017/06/01 by Andrew.Grant

	Removing some unused files to free up space across branches
	#!tests compiled locally, preflighted standard build
	#!rb none

Change 3470672 on 2017/06/01 by Daniel.Lamb

	Added new commandline argument for gauntlet which allows seperate client commands.
	Fixed realtime cooking to pass commandline options correctly to the server and client.
	#!rb None
	#!test Realtime cooking paragon

Change 3470645 on 2017/06/01 by Olaf.Piesche

	GPU sim part 2; cleanup, more bug fixing

	#!lockdown Andrew.Bains
	#!codereview simon.tovey
	#!rb none
	#!tests the usual

Change 3470636 on 2017/06/01 by Daniel.Lamb

	Improved startup time of editor by reducing number of automatic cook platforms for realtime cooking.
	#!rb Trivial
	#!test Editor paragon.

Change 3470472 on 2017/06/01 by Shaun.Kime

	Checkpointing work on compiling system and emitter graph. Very simple graphs of these types work now. No harm has befallen any of the previously working graphs.

	Some constants did change and you will MANUALLY NEED TO UPDATE any graphs referencing them.

	// Engine parameters are always read-only, no matter what level you are at.
	Engine.DeltaTime
	Engine.InverseDeltaTime
	Engine.ExecutionCount
	Engine.Owner.Position
	Engine.Owner.Velocity
	Engine.Owner.XAxis
	Engine.Owner.YAxis
	Engine.Owner.ZAxis
	Engine.Owner.LocalToWorld
	Engine.Owner.WorldToLocal
	Engine.Owner.LocalToWorldTransposed
	Engine.Owner.WorldToLocalTransposed

	// System parameters are writable in System Spawn/Update scripts and read-only otherwise.
	System.Age

	// Emitter parameters are writable in System Spawn/Update & Emitter Spawn/Update scripts and read-only otherwise.
	Emitter.Age
	Emitter.SpawnRate
	Emitter.SpawnInterval
	Emitter.InterpSpawnStartDt
	Emitter.PreviousSpawnRemainder

	#!rb none
	#!tests all existing graphs
	#!code.review frank.fella, simon.tovey, olaf.piesche

Change 3469908 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Bumped script version to grab new publishing tools
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3469901 in //Orion/Release-40.3/... via CL 3469902 via CL 3469903
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3469907 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Bumped script version to grab new publishing tools
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3469901 in //Orion/Release-40.3/... via CL 3469902 via CL 3469903
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3469906 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Bumped script version to grab new publishing tools
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3469901 in //Orion/Release-40.3/... via CL 3469902 via CL 3469903
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3469905 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Bumped script version to grab new publishing tools
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3469901 in //Orion/Release-40.3/... via CL 3469902 via CL 3469903
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3469904 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Bumped script version to grab new publishing tools
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3469901 in //Orion/Release-40.3/... via CL 3469902 via CL 3469903
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3469903 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Bumped script version to grab new publishing tools
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3469901 in //Orion/Release-40.3/... via CL 3469902
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Main)

Change 3469902 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Bumped script version to grab new publishing tools
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3469901 in //Orion/Release-40.3/...
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Release-40.4)

Change 3469901 on 2017/06/01 by Andrew.Grant

	Bumped script version to grab new publishing tools
	#!tests #!rb none

Change 3469459 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	UBT Merge from BenM:



	UBT: Fix single-file compile causing a different UHT manifest to be generated, potentially excluding hidden dependencies.

	#!rb none
	#!tests single file compile

	#!ROBOMERGE-SOURCE: CL 3469454 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3469458 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	UBT Merge from BenM:



	UBT: Fix single-file compile causing a different UHT manifest to be generated, potentially excluding hidden dependencies.

	#!rb none
	#!tests single file compile

	#!ROBOMERGE-SOURCE: CL 3469454 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3469457 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	UBT Merge from BenM:



	UBT: Fix single-file compile causing a different UHT manifest to be generated, potentially excluding hidden dependencies.

	#!rb none
	#!tests single file compile

	#!ROBOMERGE-SOURCE: CL 3469454 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3469455 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	UBT Merge from BenM:



	UBT: Fix single-file compile causing a different UHT manifest to be generated, potentially excluding hidden dependencies.

	#!rb none
	#!tests single file compile

	#!ROBOMERGE-SOURCE: CL 3469454 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3469454 on 2017/06/01 by David.Ratti

	UBT Merge from BenM:



	UBT: Fix single-file compile causing a different UHT manifest to be generated, potentially excluding hidden dependencies.

	#!rb none
	#!tests single file compile

Change 3469422 on 2017/06/01 by Nick.Darnell

	Cursor - We shouldn't try to map the cursor for "None".  Also fixing the ensure to use printf formatting.

	#!fyi Matt.Schembari
	#!rb none
	#!tests ran on PS4

Change 3469368 on 2017/06/01 by Daniel.Lamb

	Added support for precooked cook on the fly with realtime updates.
	Prefly for short.
	#!rb Andrew.Grant
	#!review-3468486 @Andrew.Grant, @Ben.Zeigler
	#!test Cook paragon, prefly paragon, shared cooked builds paragon

Change 3469261 on 2017/06/01 by Simon.Tovey

	Main thrust of this CL is to improve parameter handling for both code complexity and performance.
	Also paves the way for simple binding of parameter collections.

	- Refactored much execution work into FNiagaraScriptExecutionContext and made them persistent objects. This should be usable for system level scripts too.
	- Moved paraemter storage to use FNiagaraParameterStore. Done away with all those arrays and searching to build a final temp buffer for execution.
	- Same buffer should work for CPU and GPU.
	- Now binding directly between parameter stores to push data down into execution contexts that use it.
	- Future CL will extend systems to bind to the parameter collections they use so edits to said collection will automatically propagate down into using emtiters.
	- Changed parameter collections slightly so their instances will always have the same layout and have a copy of all the collection's data. Will remove a couple of cases where a rebind would be required at runtime.

	MISC
	- Moved stats id creation to the script itself as this data was being duplicated for every emitter.
	- Moved previous frame parameter data for interpolated spawn to the start of the parameter buffer to better fit in with other changes.
	- Various minor bug fixes.

	#!rb Shaun.Kime
	#!tests Test emitters work. Maybe a few issues with GPU sim which I'll work through with Olaf.
	#!codereview Shaun.Kime, Frank.Fella, Olaf.Piesche

Change 3469232 on 2017/06/01 by Ben.Marsh

	UBT: Fix single-file compile causing a different UHT manifest to be generated, potentially excluding hidden dependencies.

	#!rb none
	#!fyi David.Ratti
	#!tests single file compile

Change 3468842 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Changed 'ensureAlways' to 'ensure' in EnvQueryInstance.cpp #!UE4

	A temp fix for hitches in OR-39101. Looking for a root cause now.

	#!rb none
	#!test golden path
	#!jira OR-39101
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3468105 in //Orion/Release-40.3/... via CL 3468106 via CL 3468107
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3468841 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Changed 'ensureAlways' to 'ensure' in EnvQueryInstance.cpp #!UE4

	A temp fix for hitches in OR-39101. Looking for a root cause now.

	#!rb none
	#!test golden path
	#!jira OR-39101
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3468105 in //Orion/Release-40.3/... via CL 3468106 via CL 3468107
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3468840 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Changed 'ensureAlways' to 'ensure' in EnvQueryInstance.cpp #!UE4

	A temp fix for hitches in OR-39101. Looking for a root cause now.

	#!rb none
	#!test golden path
	#!jira OR-39101
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3468105 in //Orion/Release-40.3/... via CL 3468106 via CL 3468107
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3468839 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Changed 'ensureAlways' to 'ensure' in EnvQueryInstance.cpp #!UE4

	A temp fix for hitches in OR-39101. Looking for a root cause now.

	#!rb none
	#!test golden path
	#!jira OR-39101
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3468105 in //Orion/Release-40.3/... via CL 3468106 via CL 3468107
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3468838 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Changed 'ensureAlways' to 'ensure' in EnvQueryInstance.cpp #!UE4

	A temp fix for hitches in OR-39101. Looking for a root cause now.

	#!rb none
	#!test golden path
	#!jira OR-39101
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3468105 in //Orion/Release-40.3/... via CL 3468106 via CL 3468107
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3468797 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locking Release-40.2 to network CL 3464164

	#!tests #!rb na

	#!ROBOMERGE-SOURCE: CL 3467826 in //Orion/Release-40.2/... via CL 3467827 via CL 3467828 via CL 3467829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3468796 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locking Release-40.2 to network CL 3464164

	#!tests #!rb na

	#!ROBOMERGE-SOURCE: CL 3467826 in //Orion/Release-40.2/... via CL 3467827 via CL 3467828 via CL 3467829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3468795 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locking Release-40.2 to network CL 3464164

	#!tests #!rb na

	#!ROBOMERGE-SOURCE: CL 3467826 in //Orion/Release-40.2/... via CL 3467827 via CL 3467828 via CL 3467829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3468794 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locking Release-40.2 to network CL 3464164

	#!tests #!rb na

	#!ROBOMERGE-SOURCE: CL 3467826 in //Orion/Release-40.2/... via CL 3467827 via CL 3467828 via CL 3467829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3468793 on 2017/06/01 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locking Release-40.2 to network CL 3464164

	#!tests #!rb na

	#!ROBOMERGE-SOURCE: CL 3467826 in //Orion/Release-40.2/... via CL 3467827 via CL 3467828 via CL 3467829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3468661 on 2017/05/31 by Andrew.Grant

	Merging fix, mostly to get a new CL
	#!tests #!rb none

Change 3468321 on 2017/05/31 by Andrew.Grant

	Merging //Orion/Dev-General @ 3466840 to Dev-General-Playtest (//Orion/Dev-General-Playtest)
	#!tests #!rb none

Change 3468107 on 2017/05/31 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Changed 'ensureAlways' to 'ensure' in EnvQueryInstance.cpp #!UE4

	A temp fix for hitches in OR-39101. Looking for a root cause now.

	#!rb none
	#!test golden path
	#!jira OR-39101
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3468105 in //Orion/Release-40.3/... via CL 3468106
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Main)

Change 3468106 on 2017/05/31 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Changed 'ensureAlways' to 'ensure' in EnvQueryInstance.cpp #!UE4

	A temp fix for hitches in OR-39101. Looking for a root cause now.

	#!rb none
	#!test golden path
	#!jira OR-39101
	#!lockdown Andrew.Grant

	#!ROBOMERGE-SOURCE: CL 3468105 in //Orion/Release-40.3/...
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Release-40.4)

Change 3468105 on 2017/05/31 by Mieszko.Zielinski

	Changed 'ensureAlways' to 'ensure' in EnvQueryInstance.cpp #!UE4

	A temp fix for hitches in OR-39101. Looking for a root cause now.

	#!rb none
	#!test golden path
	#!jira OR-39101
	#!lockdown Andrew.Grant

Change 3467855 on 2017/05/31 by Andrew.Grant

	Removed leftover test-code
	#!tests #!rb none

Change 3467840 on 2017/05/31 by Andrew.Grant

	"redirected tag still in table" message will only be a warning if the redirected tag is not used as part of other hierarchies.

	E.g. Changing Foo to NewFoo will warn if NewFoo is still in the table, and Foo.Bar1 does not exist.

	#!review-3467804 @David.Ratti
	#!jira OR-39005
	#!tests verified warning is skipped
	#!rb none

Change 3467829 on 2017/05/31 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locking Release-40.2 to network CL 3464164

	#!tests #!rb na

	#!ROBOMERGE-SOURCE: CL 3467826 in //Orion/Release-40.2/... via CL 3467827 via CL 3467828
	#!ROBOMERGE-BOT: ORION (Release-40.4 -> Main)

Change 3467828 on 2017/05/31 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locking Release-40.2 to network CL 3464164

	#!tests #!rb na

	#!ROBOMERGE-SOURCE: CL 3467826 in //Orion/Release-40.2/... via CL 3467827
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Release-40.4)

Change 3467827 on 2017/05/31 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locking Release-40.2 to network CL 3464164

	#!tests #!rb na

	#!ROBOMERGE-SOURCE: CL 3467826 in //Orion/Release-40.2/...
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3467826 on 2017/05/31 by Andrew.Grant

	Locking Release-40.2 to network CL 3464164

	#!tests #!rb na

	#!ROBOMERGE: !40.3

Change 3467610 on 2017/05/31 by David.Ratti

	Ability System: add non debug methods for getting direct access to attribute mods.

	#!rb none
	#!tests golden path
	#!review-3467611 @Jon.Lietz

Change 3467358 on 2017/05/31 by Andrew.Grant

	Better fix for crash loading maps via content browser from TomS
	#!tests compiled, verified can still load astrolabe via content browser
	#!rb TomS

Change 3466840 on 2017/05/31 by Andrew.Grant

	Better implementation of 3466788 workaround - now append old delegates to any new ones that have been added
	#!tests opened several maps
	#!rb none

Change 3466811 on 2017/05/30 by Jeff.Williams

	Merging //Orion/Main to Release-40.4 (//Orion/Release-40.4)

	#!rb none
	#!tests none

Change 3466796 on 2017/05/30 by Jeff.Williams

	Initial branch of files from Release-40.3 (//Orion/Release-40.3) to Release-40.4 (//Orion/Release-40.4)

Change 3466788 on 2017/05/30 by Andrew.Grant

	Work-around for crash that can occur when loading a map that contains skeletal meshes via the content browser

	#!tests no longer crash loading astrolable via content browser
	#!rb none

Change 3466787 on 2017/05/30 by Andrew.Grant

	Back out revision 33 from //Orion/Dev-General/Engine/Source/Runtime/Renderer/Private/RendererScene.cpp
	#!tests #!rb none

Change 3466773 on 2017/05/30 by Andrew.Grant

	Work-around for crash loading levels from the content browser
	#!tests double-clicking Astrolobe no longer crashes
	#!rb none

Change 3466192 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed engine hitch delegate to provide source of hitch as well as duration.

	Changed OrionGameState_Moba hitch reporting to issue HITCHHUNTER logs for clients as well as servers.

	OrionGameState_Moba now checks for an elapsed time > HitchThreshold while ticking. If reported this indicated outside forces are hampering the games ability to run at framerate

	#!tests ran solo game
	#!rb none
	@jason.bestimt, @daniel.lamb

	#!ROBOMERGE-SOURCE: CL 3464148 in //Orion/Release-40.1/... via CL 3464150 via CL 3464151 via CL 3464152
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3466191 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed engine hitch delegate to provide source of hitch as well as duration.

	Changed OrionGameState_Moba hitch reporting to issue HITCHHUNTER logs for clients as well as servers.

	OrionGameState_Moba now checks for an elapsed time > HitchThreshold while ticking. If reported this indicated outside forces are hampering the games ability to run at framerate

	#!tests ran solo game
	#!rb none
	@jason.bestimt, @daniel.lamb

	#!ROBOMERGE-SOURCE: CL 3464148 in //Orion/Release-40.1/... via CL 3464150 via CL 3464151 via CL 3464152
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3466190 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed engine hitch delegate to provide source of hitch as well as duration.

	Changed OrionGameState_Moba hitch reporting to issue HITCHHUNTER logs for clients as well as servers.

	OrionGameState_Moba now checks for an elapsed time > HitchThreshold while ticking. If reported this indicated outside forces are hampering the games ability to run at framerate

	#!tests ran solo game
	#!rb none
	@jason.bestimt, @daniel.lamb

	#!ROBOMERGE-SOURCE: CL 3464148 in //Orion/Release-40.1/... via CL 3464150 via CL 3464151 via CL 3464152
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3466189 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed engine hitch delegate to provide source of hitch as well as duration.

	Changed OrionGameState_Moba hitch reporting to issue HITCHHUNTER logs for clients as well as servers.

	OrionGameState_Moba now checks for an elapsed time > HitchThreshold while ticking. If reported this indicated outside forces are hampering the games ability to run at framerate

	#!tests ran solo game
	#!rb none
	@jason.bestimt, @daniel.lamb

	#!ROBOMERGE-SOURCE: CL 3464148 in //Orion/Release-40.1/... via CL 3464150 via CL 3464151 via CL 3464152
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3466188 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed engine hitch delegate to provide source of hitch as well as duration.

	Changed OrionGameState_Moba hitch reporting to issue HITCHHUNTER logs for clients as well as servers.

	OrionGameState_Moba now checks for an elapsed time > HitchThreshold while ticking. If reported this indicated outside forces are hampering the games ability to run at framerate

	#!tests ran solo game
	#!rb none
	@jason.bestimt, @daniel.lamb

	#!ROBOMERGE-SOURCE: CL 3464148 in //Orion/Release-40.1/... via CL 3464150 via CL 3464151 via CL 3464152
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3466187 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added config setting for amount of time to spend per-frame checkpointing actors.

	Previously this was unbound (0) on Orion and would take ~60ms every second. In theory that means it needs a timeslice of 0.06ms each frame, but I'm going to be super generous and give it 4ms..

	#!tests ran local game and verified timeslice value is set and obeyed
	#!rb none
	@jason.bestimt, @daniel.lamb, @ryan.gerleve

	#!ROBOMERGE-SOURCE: CL 3464140 in //Orion/Release-40.1/... via CL 3464143 via CL 3464145 via CL 3464147
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3466186 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added config setting for amount of time to spend per-frame checkpointing actors.

	Previously this was unbound (0) on Orion and would take ~60ms every second. In theory that means it needs a timeslice of 0.06ms each frame, but I'm going to be super generous and give it 4ms..

	#!tests ran local game and verified timeslice value is set and obeyed
	#!rb none
	@jason.bestimt, @daniel.lamb, @ryan.gerleve

	#!ROBOMERGE-SOURCE: CL 3464140 in //Orion/Release-40.1/... via CL 3464143 via CL 3464145 via CL 3464147
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3466185 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added config setting for amount of time to spend per-frame checkpointing actors.

	Previously this was unbound (0) on Orion and would take ~60ms every second. In theory that means it needs a timeslice of 0.06ms each frame, but I'm going to be super generous and give it 4ms..

	#!tests ran local game and verified timeslice value is set and obeyed
	#!rb none
	@jason.bestimt, @daniel.lamb, @ryan.gerleve

	#!ROBOMERGE-SOURCE: CL 3464140 in //Orion/Release-40.1/... via CL 3464143 via CL 3464145 via CL 3464147
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3466184 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added config setting for amount of time to spend per-frame checkpointing actors.

	Previously this was unbound (0) on Orion and would take ~60ms every second. In theory that means it needs a timeslice of 0.06ms each frame, but I'm going to be super generous and give it 4ms..

	#!tests ran local game and verified timeslice value is set and obeyed
	#!rb none
	@jason.bestimt, @daniel.lamb, @ryan.gerleve

	#!ROBOMERGE-SOURCE: CL 3464140 in //Orion/Release-40.1/... via CL 3464143 via CL 3464145 via CL 3464147
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3466183 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added config setting for amount of time to spend per-frame checkpointing actors.

	Previously this was unbound (0) on Orion and would take ~60ms every second. In theory that means it needs a timeslice of 0.06ms each frame, but I'm going to be super generous and give it 4ms..

	#!tests ran local game and verified timeslice value is set and obeyed
	#!rb none
	@jason.bestimt, @daniel.lamb, @ryan.gerleve

	#!ROBOMERGE-SOURCE: CL 3464140 in //Orion/Release-40.1/... via CL 3464143 via CL 3464145 via CL 3464147
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3466182 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removed debounce period from Timeguard reporting. Unlike stat dumphitches these are low overhead so one report is not
	going to guarantee another hitch.

	#!tests ran solo game locally
	#!rb none
	@daniel.lamb, @jason.bestimt

	#!ROBOMERGE-SOURCE: CL 3464138 in //Orion/Release-40.1/... via CL 3464142 via CL 3464144 via CL 3464146
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3466181 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removed debounce period from Timeguard reporting. Unlike stat dumphitches these are low overhead so one report is not
	going to guarantee another hitch.

	#!tests ran solo game locally
	#!rb none
	@daniel.lamb, @jason.bestimt

	#!ROBOMERGE-SOURCE: CL 3464138 in //Orion/Release-40.1/... via CL 3464142 via CL 3464144 via CL 3464146
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3466180 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removed debounce period from Timeguard reporting. Unlike stat dumphitches these are low overhead so one report is not
	going to guarantee another hitch.

	#!tests ran solo game locally
	#!rb none
	@daniel.lamb, @jason.bestimt

	#!ROBOMERGE-SOURCE: CL 3464138 in //Orion/Release-40.1/... via CL 3464142 via CL 3464144 via CL 3464146
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3466177 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removed debounce period from Timeguard reporting. Unlike stat dumphitches these are low overhead so one report is not
	going to guarantee another hitch.

	#!tests ran solo game locally
	#!rb none
	@daniel.lamb, @jason.bestimt

	#!ROBOMERGE-SOURCE: CL 3464138 in //Orion/Release-40.1/... via CL 3464142 via CL 3464144 via CL 3464146
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3466176 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removed debounce period from Timeguard reporting. Unlike stat dumphitches these are low overhead so one report is not
	going to guarantee another hitch.

	#!tests ran solo game locally
	#!rb none
	@daniel.lamb, @jason.bestimt

	#!ROBOMERGE-SOURCE: CL 3464138 in //Orion/Release-40.1/... via CL 3464142 via CL 3464144 via CL 3464146
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3466175 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Moved WorldTick timeguard into world tick for clarity.
	#!tests compiled
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3464134 in //Orion/Release-40.1/... via CL 3464135 via CL 3464136 via CL 3464137
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3466172 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Moved WorldTick timeguard into world tick for clarity.
	#!tests compiled
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3464134 in //Orion/Release-40.1/... via CL 3464135 via CL 3464136 via CL 3464137
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3466171 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Moved WorldTick timeguard into world tick for clarity.
	#!tests compiled
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3464134 in //Orion/Release-40.1/... via CL 3464135 via CL 3464136 via CL 3464137
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3466170 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Moved WorldTick timeguard into world tick for clarity.
	#!tests compiled
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3464134 in //Orion/Release-40.1/... via CL 3464135 via CL 3464136 via CL 3464137
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3466169 on 2017/05/30 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Moved WorldTick timeguard into world tick for clarity.
	#!tests compiled
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3464134 in //Orion/Release-40.1/... via CL 3464135 via CL 3464136 via CL 3464137
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3465947 on 2017/05/30 by Andrew.Grant

	Initial branch of files from Dev-General (//Orion/Dev-General) to Dev-General-Playtest (//Orion/Dev-General-Playtest)

Change 3465650 on 2017/05/30 by Mieszko.Zielinski

	Plugged in Playbook-declared initial bot behaviors #!Orion

	The first behavior is going down to the jungle and placing wards
	Also:
	Implemented an Orion AITask for graph-pathfinding

	#!test golden path
	#!rb none

Change 3465622 on 2017/05/30 by Mieszko.Zielinski

	Fixed a bug in PathFollowingComponent's path segment switching that could result in wrong behavior or crashes #!UE4

	#!rb Lukasz.Furman
	#!test golden path

Change 3465382 on 2017/05/30 by Alexis.Matte

	Fix two morph target crash
	#!rb jeanmichel.dignard
	#!test none
	#!jira OR-38471

Change 3464152 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed engine hitch delegate to provide source of hitch as well as duration.

	Changed OrionGameState_Moba hitch reporting to issue HITCHHUNTER logs for clients as well as servers.

	OrionGameState_Moba now checks for an elapsed time > HitchThreshold while ticking. If reported this indicated outside forces are hampering the games ability to run at framerate

	#!tests ran solo game
	#!rb none
	@jason.bestimt, @daniel.lamb

	#!ROBOMERGE-SOURCE: CL 3464148 in //Orion/Release-40.1/... via CL 3464150 via CL 3464151
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3464151 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed engine hitch delegate to provide source of hitch as well as duration.

	Changed OrionGameState_Moba hitch reporting to issue HITCHHUNTER logs for clients as well as servers.

	OrionGameState_Moba now checks for an elapsed time > HitchThreshold while ticking. If reported this indicated outside forces are hampering the games ability to run at framerate

	#!tests ran solo game
	#!rb none
	@jason.bestimt, @daniel.lamb

	#!ROBOMERGE-SOURCE: CL 3464148 in //Orion/Release-40.1/... via CL 3464150
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3464150 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed engine hitch delegate to provide source of hitch as well as duration.

	Changed OrionGameState_Moba hitch reporting to issue HITCHHUNTER logs for clients as well as servers.

	OrionGameState_Moba now checks for an elapsed time > HitchThreshold while ticking. If reported this indicated outside forces are hampering the games ability to run at framerate

	#!tests ran solo game
	#!rb none
	@jason.bestimt, @daniel.lamb

	#!ROBOMERGE-SOURCE: CL 3464148 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3464148 on 2017/05/29 by Andrew.Grant

	Changed engine hitch delegate to provide source of hitch as well as duration.

	Changed OrionGameState_Moba hitch reporting to issue HITCHHUNTER logs for clients as well as servers.

	OrionGameState_Moba now checks for an elapsed time > HitchThreshold while ticking. If reported this indicated outside forces are hampering the games ability to run at framerate

	#!tests ran solo game
	#!rb none
	#!review-3464149 @jason.bestimt, @daniel.lamb

Change 3464147 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added config setting for amount of time to spend per-frame checkpointing actors.

	Previously this was unbound (0) on Orion and would take ~60ms every second. In theory that means it needs a timeslice of 0.06ms each frame, but I'm going to be super generous and give it 4ms..

	#!tests ran local game and verified timeslice value is set and obeyed
	#!rb none
	@jason.bestimt, @daniel.lamb, @ryan.gerleve

	#!ROBOMERGE-SOURCE: CL 3464140 in //Orion/Release-40.1/... via CL 3464143 via CL 3464145
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3464146 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removed debounce period from Timeguard reporting. Unlike stat dumphitches these are low overhead so one report is not
	going to guarantee another hitch.

	#!tests ran solo game locally
	#!rb none
	@daniel.lamb, @jason.bestimt

	#!ROBOMERGE-SOURCE: CL 3464138 in //Orion/Release-40.1/... via CL 3464142 via CL 3464144
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3464145 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added config setting for amount of time to spend per-frame checkpointing actors.

	Previously this was unbound (0) on Orion and would take ~60ms every second. In theory that means it needs a timeslice of 0.06ms each frame, but I'm going to be super generous and give it 4ms..

	#!tests ran local game and verified timeslice value is set and obeyed
	#!rb none
	@jason.bestimt, @daniel.lamb, @ryan.gerleve

	#!ROBOMERGE-SOURCE: CL 3464140 in //Orion/Release-40.1/... via CL 3464143
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3464144 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removed debounce period from Timeguard reporting. Unlike stat dumphitches these are low overhead so one report is not
	going to guarantee another hitch.

	#!tests ran solo game locally
	#!rb none
	@daniel.lamb, @jason.bestimt

	#!ROBOMERGE-SOURCE: CL 3464138 in //Orion/Release-40.1/... via CL 3464142
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3464143 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added config setting for amount of time to spend per-frame checkpointing actors.

	Previously this was unbound (0) on Orion and would take ~60ms every second. In theory that means it needs a timeslice of 0.06ms each frame, but I'm going to be super generous and give it 4ms..

	#!tests ran local game and verified timeslice value is set and obeyed
	#!rb none
	@jason.bestimt, @daniel.lamb, @ryan.gerleve

	#!ROBOMERGE-SOURCE: CL 3464140 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3464142 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Removed debounce period from Timeguard reporting. Unlike stat dumphitches these are low overhead so one report is not
	going to guarantee another hitch.

	#!tests ran solo game locally
	#!rb none
	@daniel.lamb, @jason.bestimt

	#!ROBOMERGE-SOURCE: CL 3464138 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3464140 on 2017/05/29 by Andrew.Grant

	Added config setting for amount of time to spend per-frame checkpointing actors.

	Previously this was unbound (0) on Orion and would take ~60ms every second. In theory that means it needs a timeslice of 0.06ms each frame, but I'm going to be super generous and give it 4ms..

	#!tests ran local game and verified timeslice value is set and obeyed
	#!rb none
	#!review-3464141 @jason.bestimt, @daniel.lamb, @ryan.gerleve

Change 3464138 on 2017/05/29 by Andrew.Grant

	Removed debounce period from Timeguard reporting. Unlike stat dumphitches these are low overhead so one report is not
	going to guarantee another hitch.

	#!tests ran solo game locally
	#!rb none
	#!review-3464139 @daniel.lamb, @jason.bestimt

Change 3464137 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Moved WorldTick timeguard into world tick for clarity.
	#!tests compiled
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3464134 in //Orion/Release-40.1/... via CL 3464135 via CL 3464136
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3464136 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Moved WorldTick timeguard into world tick for clarity.
	#!tests compiled
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3464134 in //Orion/Release-40.1/... via CL 3464135
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3464135 on 2017/05/29 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Moved WorldTick timeguard into world tick for clarity.
	#!tests compiled
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3464134 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3464134 on 2017/05/29 by Andrew.Grant

	Moved WorldTick timeguard into world tick for clarity.
	#!tests compiled
	#!rb none

Change 3463889 on 2017/05/28 by David.Ratti

	refactor GE creation menu code to be less nesty
	#!rb none
	#!tests compiles on my machine

Change 3462711 on 2017/05/26 by David.Ratti

	Ensure unique asset name when creating GEs through GE creation menu (currently disabled until builder issue sorted)

	#!rb none
	#!tests editor

Change 3462619 on 2017/05/26 by Olaf.Piesche

	GPU sim work - WARNING: WORK IN PROGRESS

	You can get something on screen, but there's cleanup and bug fixing still left to do. Trying to get this checked in to avoid more merging problems in the near future. GPU dispatch execution works, rendering of sprites no longer creates an explicit vertex buffer and should be quite a bit faster for CPU sim as well.
	Still working on getting the sim step moved over entirely to the simulation batcher; currently, this has all sorts of problems with GPU sim, so please be advised that switching an emitter to GPU sim will currently not work with anything that uses data interfaces AND MAY CRASH YOUR MACHINE in rare instances.  I'm working on finalizing the remaining steps.

	tl;dr: CPU simulation should be unaffected. CPU rendering of sprites should be faster. GPU sim may make the universe implode.

	#!tests checked test emitters in CPU mode, ran GPUTest in GPU mode (works with known bugs when spawning)
	#!lockdown andrew.bains
	#!codereview simon.tovey
	#!rb none

Change 3462617 on 2017/05/26 by Matt.Kuhlenschmidt

	Exposed new methods of adding a struct on scope to a details panel and have it work properly with customizations.
	Refactored the niagrata script panel to use a proper details customization instead of custom widgets

	#!rb frank.fella
	#!tests niagara

Change 3462568 on 2017/05/26 by Andrew.Grant

	Disabling UGameplayEffectCreationMenu::AddMenuExtensions to get a build out.
	#!tests #!rb none

Change 3462372 on 2017/05/26 by Andrew.Grant

	Disable optimizations around this function to see if it prevents internal compiler errors on build machines.

	(Could be due to builders not running VS2015 SP3)

	#!tests compiled locally
	#!rb none
	#!review-3462373 @David.Ratti

Change 3462362 on 2017/05/26 by David.Ratti

	Fix for periodic damage GEs not properly pushing a GE context when they tick/execute. Was causing warnings / qualifiers to no work on periodic GEs.

	#!rb none
	#!tests pie
	#!review-3462364 @Jon.Lietz

Change 3462161 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: paul.moore
	[MatchMaking]
	- Merging MMS changes from DevGeneral to Main for v40.5.
	#!tests matchmaking, solo match, PS4 #!rb none
	#!lockdown andrew.grant

	#!ROBOMERGE-SOURCE: CL 3461655 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3462160 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: paul.moore
	[MatchMaking]
	- Merging MMS changes from DevGeneral to Main for v40.5.
	#!tests matchmaking, solo match, PS4 #!rb none
	#!lockdown andrew.grant

	#!ROBOMERGE-SOURCE: CL 3461655 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3462159 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: paul.moore
	[MatchMaking]
	- Merging MMS changes from DevGeneral to Main for v40.5.
	#!tests matchmaking, solo match, PS4 #!rb none
	#!lockdown andrew.grant

	#!ROBOMERGE-SOURCE: CL 3461655 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3462158 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: paul.moore
	[MatchMaking]
	- Merging MMS changes from DevGeneral to Main for v40.5.
	#!tests matchmaking, solo match, PS4 #!rb none
	#!lockdown andrew.grant

	#!ROBOMERGE-SOURCE: CL 3461655 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3461941 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed issue where test reports could fail
	Minor tweaks to adjust time before hitch warnings occur to be more generous and prevent false positives
	Only show loaded mcp items during an object report
	#!tests ran soak test
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3460175 in //Orion/Release-40.1/... via CL 3460176 via CL 3460177 via CL 3460178
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3461940 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed issue where test reports could fail
	Minor tweaks to adjust time before hitch warnings occur to be more generous and prevent false positives
	Only show loaded mcp items during an object report
	#!tests ran soak test
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3460175 in //Orion/Release-40.1/... via CL 3460176 via CL 3460177 via CL 3460178
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3461939 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed issue where test reports could fail
	Minor tweaks to adjust time before hitch warnings occur to be more generous and prevent false positives
	Only show loaded mcp items during an object report
	#!tests ran soak test
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3460175 in //Orion/Release-40.1/... via CL 3460176 via CL 3460177 via CL 3460178
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3461938 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed issue where test reports could fail
	Minor tweaks to adjust time before hitch warnings occur to be more generous and prevent false positives
	Only show loaded mcp items during an object report
	#!tests ran soak test
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3460175 in //Orion/Release-40.1/... via CL 3460176 via CL 3460177 via CL 3460178
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3461937 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed issue where test reports could fail
	Minor tweaks to adjust time before hitch warnings occur to be more generous and prevent false positives
	Only show loaded mcp items during an object report
	#!tests ran soak test
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3460175 in //Orion/Release-40.1/... via CL 3460176 via CL 3460177 via CL 3460178
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3461868 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Physics PreTick timeguard to something that seems more appropriate

	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3459699 in //Orion/Release-40.1/... via CL 3459701 via CL 3459702 via CL 3459703
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3461867 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Physics PreTick timeguard to something that seems more appropriate

	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3459699 in //Orion/Release-40.1/... via CL 3459701 via CL 3459702 via CL 3459703
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3461866 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Physics PreTick timeguard to something that seems more appropriate

	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3459699 in //Orion/Release-40.1/... via CL 3459701 via CL 3459702 via CL 3459703
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3461865 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Physics PreTick timeguard to something that seems more appropriate

	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3459699 in //Orion/Release-40.1/... via CL 3459701 via CL 3459702 via CL 3459703
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3461861 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Physics PreTick timeguard to something that seems more appropriate

	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3459699 in //Orion/Release-40.1/... via CL 3459701 via CL 3459702 via CL 3459703
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3461655 on 2017/05/26 by Paul.Moore

	[MatchMaking]
	- Merging MMS changes from DevGeneral to Main for v40.5.
	#!tests matchmaking, solo match, PS4 #!rb none
	#!lockdown andrew.grant

Change 3461648 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added TimeGuard's to more points in World Tick

	#!tests compiled server, ran locally
	#!rb none
	@David.Ratti

	#!ROBOMERGE-SOURCE: CL 3457691 in //Orion/Release-40.1/... via CL 3457695 via CL 3457696 via CL 3457697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3461645 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added TimeGuard's to more points in World Tick

	#!tests compiled server, ran locally
	#!rb none
	@David.Ratti

	#!ROBOMERGE-SOURCE: CL 3457691 in //Orion/Release-40.1/... via CL 3457695 via CL 3457696 via CL 3457697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3461644 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added TimeGuard's to more points in World Tick

	#!tests compiled server, ran locally
	#!rb none
	@David.Ratti

	#!ROBOMERGE-SOURCE: CL 3457691 in //Orion/Release-40.1/... via CL 3457695 via CL 3457696 via CL 3457697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3461643 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added TimeGuard's to more points in World Tick

	#!tests compiled server, ran locally
	#!rb none
	@David.Ratti

	#!ROBOMERGE-SOURCE: CL 3457691 in //Orion/Release-40.1/... via CL 3457695 via CL 3457696 via CL 3457697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3461642 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added TimeGuard's to more points in World Tick

	#!tests compiled server, ran locally
	#!rb none
	@David.Ratti

	#!ROBOMERGE-SOURCE: CL 3457691 in //Orion/Release-40.1/... via CL 3457695 via CL 3457696 via CL 3457697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3461598 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Stability improvements to EnvironmentPerfTest
	#!tests ran test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3457367 in //Orion/Release-40.1/... via CL 3457369 via CL 3457370 via CL 3457371
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3461597 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Stability improvements to EnvironmentPerfTest
	#!tests ran test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3457367 in //Orion/Release-40.1/... via CL 3457369 via CL 3457370 via CL 3457371
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3461596 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Stability improvements to EnvironmentPerfTest
	#!tests ran test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3457367 in //Orion/Release-40.1/... via CL 3457369 via CL 3457370 via CL 3457371
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3461595 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Stability improvements to EnvironmentPerfTest
	#!tests ran test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3457367 in //Orion/Release-40.1/... via CL 3457369 via CL 3457370 via CL 3457371
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3461594 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Stability improvements to EnvironmentPerfTest
	#!tests ran test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3457367 in //Orion/Release-40.1/... via CL 3457369 via CL 3457370 via CL 3457371
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3461566 on 2017/05/26 by Andrew.Grant

	Merging blocked robomerge change from //Orion/Main to Dev-UI (//Orion/Dev-UI)
	#!tests #!rb none

Change 3461507 on 2017/05/26 by andrew.grant

	Merging some files from //Orion/Release-40.3 that were left stranded
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456847 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

	#!ROBOMERGE-SAYS: Unresolved conflicts. andrew.grant, please merge this change by hand.
	//ROBOMERGE_ORION_Dev_General/OrionGame/Source/OrionGame/OrionEngine.h
	#!CodeReview: andrew.grant, jason.bestimt, jeff.williams

Change 3461500 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Merging some files from //Orion/Release-40.3 that were left stranded
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456847 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3461499 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Merging some files from //Orion/Release-40.3 that were left stranded
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456847 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3461498 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Merging some files from //Orion/Release-40.3 that were left stranded
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456847 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3461495 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Add better way of getting peak memory for test report
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3456821 in //Orion/Release-40.1/... via CL 3456822 via CL 3456823 via CL 3456829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3461494 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Add better way of getting peak memory for test report
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3456821 in //Orion/Release-40.1/... via CL 3456822 via CL 3456823 via CL 3456829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3461493 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Add better way of getting peak memory for test report
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3456821 in //Orion/Release-40.1/... via CL 3456822 via CL 3456823 via CL 3456829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3461492 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Add better way of getting peak memory for test report
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3456821 in //Orion/Release-40.1/... via CL 3456822 via CL 3456823 via CL 3456829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3461491 on 2017/05/26 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Add better way of getting peak memory for test report
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3456821 in //Orion/Release-40.1/... via CL 3456822 via CL 3456823 via CL 3456829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3461467 on 2017/05/26 by David.Ratti

	GameplayEffectCreationMenu
	Data driven way to add heirachial list of common parent GEs that is accessible through content browser's right click menus

	Designers can maintain configable list of gameplay effects they want to appear in these menus.

	#!rb none
	#!tests editor
	#!review-3461469 @Billy.Bramer

Change 3461385 on 2017/05/26 by David.Ratti

	Change FContentBrowserModule::AssetContextMenuExtenders to use FContentBrowserMenuExtender_SelectedPaths delegate types. This enables extenders to get current path of the content browser.

	#!review-3461386 @Jamie.Dale
	#!rb none
	#!tests editor

Change 3461347 on 2017/05/26 by Andrew.Grant

	Restored deprecation mark
	#!rb #!tests none

Change 3461343 on 2017/05/26 by Don.Eubanks

	Added in some Analog Cursor features from Fortnite.

	OrionAnalogCursor now supports an "auto hover" mode, where Navigation events cause the cursor to be teleported to the center of the destination widget.  In Orion specifically we support using the left stick to transition out of Auto Hover mode back into regular analog cursor mode.

	Not-yet-implemented features:
	  * Need better resuming when transitioning from stick to d-pad, currently things you hover are not automatically focused, but they should be so that navigation will pick up at the right spot.
	  * Cursor doesn't properly fully hide on PC in PIE (potentially also in Client), needs more investigation.

	Added some better hover coloring / state data in Card Shop / Attribute Row so the d-pad highlighting is more apparent.

	#!rb philip.buuck
	#!tests Used d-pad to navigate through Card Shop, verified transition to sticks and back.  Verified that the feature does not work in the FrontEnd.

Change 3460684 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Unshelved from pending changelist '3456731':

	Improved memory test reporting and added support for running against older builds
	#!test ran test on old 39.5 build
	#!rb  none


	//ROBOMERGE_ORION_Main/Engine/Source/Programs/AutomationTool/NotForLicensees/Gauntlet/Orion/Tests/OrionTest.BaselinePerformance.cs
	[CODEREVIEW] andrew.grant, jason.bestimt, jeff.williams

	#!ROBOMERGE-SOURCE: CL 3456726 in //Orion/Release-40.1/... via CL 3456729 via CL 3456730 via CL 3456756
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3460683 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Unshelved from pending changelist '3456731':

	Improved memory test reporting and added support for running against older builds
	#!test ran test on old 39.5 build
	#!rb  none


	//ROBOMERGE_ORION_Main/Engine/Source/Programs/AutomationTool/NotForLicensees/Gauntlet/Orion/Tests/OrionTest.BaselinePerformance.cs
	[CODEREVIEW] andrew.grant, jason.bestimt, jeff.williams

	#!ROBOMERGE-SOURCE: CL 3456726 in //Orion/Release-40.1/... via CL 3456729 via CL 3456730 via CL 3456756
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3460682 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Unshelved from pending changelist '3456731':

	Improved memory test reporting and added support for running against older builds
	#!test ran test on old 39.5 build
	#!rb  none


	//ROBOMERGE_ORION_Main/Engine/Source/Programs/AutomationTool/NotForLicensees/Gauntlet/Orion/Tests/OrionTest.BaselinePerformance.cs
	[CODEREVIEW] andrew.grant, jason.bestimt, jeff.williams

	#!ROBOMERGE-SOURCE: CL 3456726 in //Orion/Release-40.1/... via CL 3456729 via CL 3456730 via CL 3456756
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3460681 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Unshelved from pending changelist '3456731':

	Improved memory test reporting and added support for running against older builds
	#!test ran test on old 39.5 build
	#!rb  none


	//ROBOMERGE_ORION_Main/Engine/Source/Programs/AutomationTool/NotForLicensees/Gauntlet/Orion/Tests/OrionTest.BaselinePerformance.cs
	[CODEREVIEW] andrew.grant, jason.bestimt, jeff.williams

	#!ROBOMERGE-SOURCE: CL 3456726 in //Orion/Release-40.1/... via CL 3456729 via CL 3456730 via CL 3456756
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3460680 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Unshelved from pending changelist '3456731':

	Improved memory test reporting and added support for running against older builds
	#!test ran test on old 39.5 build
	#!rb  none


	//ROBOMERGE_ORION_Main/Engine/Source/Programs/AutomationTool/NotForLicensees/Gauntlet/Orion/Tests/OrionTest.BaselinePerformance.cs
	[CODEREVIEW] andrew.grant, jason.bestimt, jeff.williams

	#!ROBOMERGE-SOURCE: CL 3456726 in //Orion/Release-40.1/... via CL 3456729 via CL 3456730 via CL 3456756
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3460654 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Version locked v40.1 to 3452376
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456644 in //Orion/Release-40.1/... via CL 3456645 via CL 3456649 via CL 3456650
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3460653 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Version locked v40.1 to 3452376
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456644 in //Orion/Release-40.1/... via CL 3456645 via CL 3456649 via CL 3456650
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3460652 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Version locked v40.1 to 3452376
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456644 in //Orion/Release-40.1/... via CL 3456645 via CL 3456649 via CL 3456650
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3460651 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Version locked v40.1 to 3452376
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456644 in //Orion/Release-40.1/... via CL 3456645 via CL 3456649 via CL 3456650
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3460650 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Version locked v40.1 to 3452376
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456644 in //Orion/Release-40.1/... via CL 3456645 via CL 3456649 via CL 3456650
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3460649 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Merge support for flat additive attribute channel from CL 3454524

	#!rb none
	#!test compile

	#!ROBOMERGE-SOURCE: CL 3456500 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3460648 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Merge support for flat additive attribute channel from CL 3454524

	#!rb none
	#!test compile

	#!ROBOMERGE-SOURCE: CL 3456500 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3460647 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Merge support for flat additive attribute channel from CL 3454524

	#!rb none
	#!test compile

	#!ROBOMERGE-SOURCE: CL 3456500 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3460645 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Merge support for flat additive attribute channel from CL 3454524

	#!rb none
	#!test compile

	#!ROBOMERGE-SOURCE: CL 3456500 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3460428 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Include TimeSinceBoot in memreport, and PS4 heap sizes in mem report
	#!tests Local memory testing
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3455640 in //Orion/Release-40.1/... via CL 3455642 via CL 3455697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3460427 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Include TimeSinceBoot in memreport, and PS4 heap sizes in mem report
	#!tests Local memory testing
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3455640 in //Orion/Release-40.1/... via CL 3455642 via CL 3455697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3460426 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Include TimeSinceBoot in memreport, and PS4 heap sizes in mem report
	#!tests Local memory testing
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3455640 in //Orion/Release-40.1/... via CL 3455642 via CL 3455697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3460425 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Include TimeSinceBoot in memreport, and PS4 heap sizes in mem report
	#!tests Local memory testing
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3455640 in //Orion/Release-40.1/... via CL 3455642 via CL 3455697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3460424 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Include TimeSinceBoot in memreport, and PS4 heap sizes in mem report
	#!tests Local memory testing
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3455640 in //Orion/Release-40.1/... via CL 3455642 via CL 3455697
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3460398 on 2017/05/25 by Andrew.Grant

	Fix for non-unity issues
	#!tests #!rb none

Change 3460178 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed issue where test reports could fail
	Minor tweaks to adjust time before hitch warnings occur to be more generous and prevent false positives
	Only show loaded mcp items during an object report
	#!tests ran soak test
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3460175 in //Orion/Release-40.1/... via CL 3460176 via CL 3460177
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3460177 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed issue where test reports could fail
	Minor tweaks to adjust time before hitch warnings occur to be more generous and prevent false positives
	Only show loaded mcp items during an object report
	#!tests ran soak test
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3460175 in //Orion/Release-40.1/... via CL 3460176
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3460176 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed issue where test reports could fail
	Minor tweaks to adjust time before hitch warnings occur to be more generous and prevent false positives
	Only show loaded mcp items during an object report
	#!tests ran soak test
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3460175 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3460175 on 2017/05/25 by Andrew.Grant

	Fixed issue where test reports could fail
	Minor tweaks to adjust time before hitch warnings occur to be more generous and prevent false positives
	Only show loaded mcp items during an object report
	#!tests ran soak test
	#!rb none

Change 3460120 on 2017/05/25 by Alexis.Matte

	Fix Unregistering of SelectLodChanged delegate for staticmesh editor
	#!jira UE-45346
	#!rb none
	#!tests none

Change 3459820 on 2017/05/25 by Shaun.Kime

	Compile error fix
	#!rb none
	#!tests n/a

Change 3459703 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Physics PreTick timeguard to something that seems more appropriate

	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3459699 in //Orion/Release-40.1/... via CL 3459701 via CL 3459702
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3459702 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Physics PreTick timeguard to something that seems more appropriate

	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3459699 in //Orion/Release-40.1/... via CL 3459701
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3459701 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Physics PreTick timeguard to something that seems more appropriate

	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3459699 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3459699 on 2017/05/25 by Andrew.Grant

	Changed Physics PreTick timeguard to something that seems more appropriate

	#!tests ran locally
	#!rb none

Change 3459190 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Tweaked MemoryReport test
	- Always dump a memreport on a state change (very useful for comparing two builds)
	- Only dump leak/alloc reports if > 1m into the game (While notimeouts stops the game disconnecting, draft and moba games don't do well if the client is non-responsive).

	#!tests ran MemReport test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3452458 in //Orion/Release-40.1/... via CL 3452461 via CL 3452484
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3459189 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Tweaked MemoryReport test
	- Always dump a memreport on a state change (very useful for comparing two builds)
	- Only dump leak/alloc reports if > 1m into the game (While notimeouts stops the game disconnecting, draft and moba games don't do well if the client is non-responsive).

	#!tests ran MemReport test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3452458 in //Orion/Release-40.1/... via CL 3452461 via CL 3452484
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3459188 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Tweaked MemoryReport test
	- Always dump a memreport on a state change (very useful for comparing two builds)
	- Only dump leak/alloc reports if > 1m into the game (While notimeouts stops the game disconnecting, draft and moba games don't do well if the client is non-responsive).

	#!tests ran MemReport test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3452458 in //Orion/Release-40.1/... via CL 3452461 via CL 3452484
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3459187 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Tweaked MemoryReport test
	- Always dump a memreport on a state change (very useful for comparing two builds)
	- Only dump leak/alloc reports if > 1m into the game (While notimeouts stops the game disconnecting, draft and moba games don't do well if the client is non-responsive).

	#!tests ran MemReport test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3452458 in //Orion/Release-40.1/... via CL 3452461 via CL 3452484
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3459186 on 2017/05/25 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Tweaked MemoryReport test
	- Always dump a memreport on a state change (very useful for comparing two builds)
	- Only dump leak/alloc reports if > 1m into the game (While notimeouts stops the game disconnecting, draft and moba games don't do well if the client is non-responsive).

	#!tests ran MemReport test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3452458 in //Orion/Release-40.1/... via CL 3452461 via CL 3452484
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3458973 on 2017/05/25 by Lina.Halper

	Slave mesh component not clearing morphtarget

	#!rb: Martin.Wilson
	#!jira: https://jira.it.epicgames.net/browse/OR-38475
	#!tests: PIE with Wukong

Change 3457697 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added TimeGuard's to more points in World Tick

	#!tests compiled server, ran locally
	#!rb none
	@David.Ratti

	#!ROBOMERGE-SOURCE: CL 3457691 in //Orion/Release-40.1/... via CL 3457695 via CL 3457696
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3457696 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added TimeGuard's to more points in World Tick

	#!tests compiled server, ran locally
	#!rb none
	@David.Ratti

	#!ROBOMERGE-SOURCE: CL 3457691 in //Orion/Release-40.1/... via CL 3457695
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3457695 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added TimeGuard's to more points in World Tick

	#!tests compiled server, ran locally
	#!rb none
	@David.Ratti

	#!ROBOMERGE-SOURCE: CL 3457691 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3457691 on 2017/05/24 by Andrew.Grant

	Added TimeGuard's to more points in World Tick

	#!tests compiled server, ran locally
	#!rb none
	#!review-3457692 @David.Ratti

Change 3457371 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Stability improvements to EnvironmentPerfTest
	#!tests ran test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3457367 in //Orion/Release-40.1/... via CL 3457369 via CL 3457370
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3457370 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Stability improvements to EnvironmentPerfTest
	#!tests ran test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3457367 in //Orion/Release-40.1/... via CL 3457369
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3457369 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Stability improvements to EnvironmentPerfTest
	#!tests ran test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3457367 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3457367 on 2017/05/24 by Andrew.Grant

	Stability improvements to EnvironmentPerfTest
	#!tests ran test locally
	#!rb none

Change 3457310 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed typo in obj command (non-shipping change
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3451906 in //Orion/Release-40.1/... via CL 3451908 via CL 3451912
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3457307 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed typo in obj command (non-shipping change
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3451906 in //Orion/Release-40.1/... via CL 3451908 via CL 3451912
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3457306 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed typo in obj command (non-shipping change
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3451906 in //Orion/Release-40.1/... via CL 3451908 via CL 3451912
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3457305 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed typo in obj command (non-shipping change
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3451906 in //Orion/Release-40.1/... via CL 3451908 via CL 3451912
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3457304 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed typo in obj command (non-shipping change
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3451906 in //Orion/Release-40.1/... via CL 3451908 via CL 3451912
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3457028 on 2017/05/24 by Andrew.Grant

	Copying fix for hidden window perf from 4.16 branch
	#!tests #!rb none

Change 3456896 on 2017/05/24 by Alexis.Matte

	Fix crash when adding LOD in a static mesh
	#!jira UE-45346
	#!rb none
	#!tests none

Change 3456853 on 2017/05/24 by Laurent.Delayen

	Fix for crash in FAnimationRuntime::CreateMaskWeights when MaskBoneIndex is not valid.

	#!rb none
	#!codereview lina.halper
	#!tests Medic in Monolith.

Change 3456847 on 2017/05/24 by Andrew.Grant

	Merging some files from //Orion/Release-40.3 that were left stranded
	#!tests #!rb none

Change 3456829 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Add better way of getting peak memory for test report
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3456821 in //Orion/Release-40.1/... via CL 3456822 via CL 3456823
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3456823 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Add better way of getting peak memory for test report
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3456821 in //Orion/Release-40.1/... via CL 3456822
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3456822 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Add better way of getting peak memory for test report
	#!tests ran locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3456821 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3456821 on 2017/05/24 by Andrew.Grant

	Add better way of getting peak memory for test report
	#!tests ran locally
	#!rb none

Change 3456811 on 2017/05/24 by Frank.Fella

	Niagara - Fix stack overflow when calling GetParameterMaps for a graph.

	#!tests No longer has a stack overflow.
	#!rb Shaun.Kime

Change 3456756 on 2017/05/24 by Andrew.Grant

	Unshelved from pending changelist '3456731':

	Improved memory test reporting and added support for running against older builds
	#!test ran test on old 39.5 build
	#!rb  none

	#!ROBOMERGE-SOURCE: CL 3456726 in //Orion/Release-40.1/... via CL 3456729 via CL 3456730
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

	#!ROBOMERGE-SAYS: Unresolved conflicts. andrew.grant, please merge this change by hand.
	//ROBOMERGE_ORION_Main/Engine/Source/Programs/AutomationTool/NotForLicensees/Gauntlet/Orion/Tests/OrionTest.BaselinePerformance.cs
	#!CodeReview: andrew.grant, jason.bestimt, jeff.williams

Change 3456730 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Improved memory test reporting and added support for running against older builds
	#!test ran test on old 39.5 build
	#!rb  none

	#!ROBOMERGE-SOURCE: CL 3456726 in //Orion/Release-40.1/... via CL 3456729
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3456729 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Improved memory test reporting and added support for running against older builds
	#!test ran test on old 39.5 build
	#!rb  none

	#!ROBOMERGE-SOURCE: CL 3456726 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3456726 on 2017/05/24 by Andrew.Grant

	Improved memory test reporting and added support for running against older builds
	#!test ran test on old 39.5 build
	#!rb  none

Change 3456650 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Version locked v40.1 to 3452376
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456644 in //Orion/Release-40.1/... via CL 3456645 via CL 3456649
	#!ROBOMERGE-BOT: ORION (Release-40.3 -> Main)

Change 3456649 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Version locked v40.1 to 3452376
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456644 in //Orion/Release-40.1/... via CL 3456645
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Release-40.3)

Change 3456645 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Version locked v40.1 to 3452376
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3456644 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3456644 on 2017/05/24 by Andrew.Grant

	Version locked v40.1 to 3452376
	#!tests #!rb none
	#!ROBOMERGE: !40.2

Change 3456609 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Allow branch & CL to be passed into Gauntlet for reporting
	Pass branch and CL in to Gauntlet for editor tests so logs end up under branch folder
	#!tests ran editor tests locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3449827 in //Orion/Release-40.1/... via CL 3449828 via CL 3449829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3456608 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Allow branch & CL to be passed into Gauntlet for reporting
	Pass branch and CL in to Gauntlet for editor tests so logs end up under branch folder
	#!tests ran editor tests locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3449827 in //Orion/Release-40.1/... via CL 3449828 via CL 3449829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3456607 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Allow branch & CL to be passed into Gauntlet for reporting
	Pass branch and CL in to Gauntlet for editor tests so logs end up under branch folder
	#!tests ran editor tests locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3449827 in //Orion/Release-40.1/... via CL 3449828 via CL 3449829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3456606 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Allow branch & CL to be passed into Gauntlet for reporting
	Pass branch and CL in to Gauntlet for editor tests so logs end up under branch folder
	#!tests ran editor tests locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3449827 in //Orion/Release-40.1/... via CL 3449828 via CL 3449829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3456605 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Allow branch & CL to be passed into Gauntlet for reporting
	Pass branch and CL in to Gauntlet for editor tests so logs end up under branch folder
	#!tests ran editor tests locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3449827 in //Orion/Release-40.1/... via CL 3449828 via CL 3449829
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3456575 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Properly exposing bSingleSampleShadowFromStationaryLights to BP

	[CODEREVIEW] Daniel.Wright
	#!rb none
	#!tests compile

	#!ROBOMERGE-SOURCE: CL 3449606 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3456574 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Properly exposing bSingleSampleShadowFromStationaryLights to BP

	[CODEREVIEW] Daniel.Wright
	#!rb none
	#!tests compile

	#!ROBOMERGE-SOURCE: CL 3449606 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3456573 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Properly exposing bSingleSampleShadowFromStationaryLights to BP

	[CODEREVIEW] Daniel.Wright
	#!rb none
	#!tests compile

	#!ROBOMERGE-SOURCE: CL 3449606 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3456572 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Properly exposing bSingleSampleShadowFromStationaryLights to BP

	[CODEREVIEW] Daniel.Wright
	#!rb none
	#!tests compile

	#!ROBOMERGE-SOURCE: CL 3449606 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3456571 on 2017/05/24 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Properly exposing bSingleSampleShadowFromStationaryLights to BP

	[CODEREVIEW] Daniel.Wright
	#!rb none
	#!tests compile

	#!ROBOMERGE-SOURCE: CL 3449606 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3456500 on 2017/05/24 by David.Ratti

	Merge support for flat additive attribute channel from CL 3454524

	#!rb none
	#!test compile

Change 3456463 on 2017/05/24 by Simon.Tovey

	Parameter collections phase 3.

	Instances and beginnings of improved storage for all parameters.

	#!codereview Frank.Fella, Shaun.Kime
	#!rb Frank.Fella, Shaun.Kime
	#!tests Asset and editor appear to be working. Few rough edges and bugs I'm sure.

Change 3456212 on 2017/05/24 by Jeff.Williams

	Merging //Orion/Main to Release-40.3 (//Orion/Release-40.3) @3456007

	#!rb none
	#!tests none

Change 3456197 on 2017/05/24 by Jeff.Williams

	Initial branch of files from Release-40.2 (//Orion/Release-40.2) to Release-40.3 (//Orion/Release-40.3)

Change 3456182 on 2017/05/24 by Andrew.Grant

	Merging 3456174 from 40.1 due to Robomerge being down.

	Added memory reporting at certain stages of engine lifecycle
	Updated BaselinePerformance report to save memory values to new spreadsheet

	#!tests ran BaselinePerformance locally
	#!rb none

Change 3456174 on 2017/05/24 by Andrew.Grant

	Added memory reporting at certain stages of engine lifecycle
	Updated BaselinePerformance report to save memory values to new spreadsheet

	#!tests ran BaselinePerformance locally
	#!rb none
	#!review-3456175 @Daniel.Lamb

Change 3456005 on 2017/05/23 by Matt.Schembari

	Invisible PS4 Cursor Bug -- we're getting louder
	- Added ensures for all the failure cases in GameViewportClient to help capture this.
	- Added tracing logs for the different cases that can cause values to change in OrionGameViewportClient.

	#!review-3456006 @nick.darnell, @andrew.grant

	#!rb none
	#!tests PIE and standalone, making sure we don't hit the ensures and that the logs are working

	#!QAReview This is to help with bug OR-36760. If anybody hits this OR sees and invisible cursor, capture logs and immediately reach out to me.

Change 3455797 on 2017/05/23 by Frank.Fella

	Niagara - Maintain the desired age of an effect instance when paused and resetting directly, or when seeking backwards.

	#!tests When resetting or seeking backward on an effect which is paused in the editor, the viewport no longer goes black, and the effect simulates to the correct time.
	#!rb none
	#!codereview Olaf.Piesche,Simon.Tovey,Shaun.Kime

Change 3455697 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Include TimeSinceBoot in memreport, and PS4 heap sizes in mem report
	#!tests Local memory testing
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3455640 in //Orion/Release-40.1/... via CL 3455642
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3455642 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Include TimeSinceBoot in memreport, and PS4 heap sizes in mem report
	#!tests Local memory testing
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3455640 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3455640 on 2017/05/23 by Andrew.Grant

	Include TimeSinceBoot in memreport, and PS4 heap sizes in mem report
	#!tests Local memory testing
	#!rb none

Change 3455634 on 2017/05/23 by Frank.Fella

	Niagara - Stack - Usability/style pass
	+ Move colors and brushes to the style class.
	+ Add a single expander to the bottom of module items which hides/shows the unpinned input/output collections.
	+ Adjust padding, background colors, and fonts to increase readability.
	+ Change the function call node title to format the name for display.

	#!tests The ui is more readable.
	#!rb none

Change 3455580 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Gauntlet file copy to use parallel-for with 2 threads. Takes deploy time down from ~14m to 11m

	#!rb none
	@Daniel.Lamb
	#!tests deployed locally staged and network builds

	#!ROBOMERGE-SOURCE: CL 3449370 in //Orion/Release-40.1/... via CL 3449372 via CL 3449474
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3455579 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Gauntlet file copy to use parallel-for with 2 threads. Takes deploy time down from ~14m to 11m

	#!rb none
	@Daniel.Lamb
	#!tests deployed locally staged and network builds

	#!ROBOMERGE-SOURCE: CL 3449370 in //Orion/Release-40.1/... via CL 3449372 via CL 3449474
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3455578 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Gauntlet file copy to use parallel-for with 2 threads. Takes deploy time down from ~14m to 11m

	#!rb none
	@Daniel.Lamb
	#!tests deployed locally staged and network builds

	#!ROBOMERGE-SOURCE: CL 3449370 in //Orion/Release-40.1/... via CL 3449372 via CL 3449474
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3455577 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Gauntlet file copy to use parallel-for with 2 threads. Takes deploy time down from ~14m to 11m

	#!rb none
	@Daniel.Lamb
	#!tests deployed locally staged and network builds

	#!ROBOMERGE-SOURCE: CL 3449370 in //Orion/Release-40.1/... via CL 3449372 via CL 3449474
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3455576 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Gauntlet file copy to use parallel-for with 2 threads. Takes deploy time down from ~14m to 11m

	#!rb none
	@Daniel.Lamb
	#!tests deployed locally staged and network builds

	#!ROBOMERGE-SOURCE: CL 3449370 in //Orion/Release-40.1/... via CL 3449372 via CL 3449474
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3455560 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Fix tag issue. FName comparison on instances FName("A") not consistent between platforms due to static init order. Sorting should be done on the full tag name, which is unique for the gameplay tag system. (Vs the simple tag, which are the "subtags" which are not unique. End result is a bunch of comparisons on FName("A") instances not being the same between platforms).

	#!rb none
	@Andrew.Grant
	#!tests PS4 + Dedicated server (verified tag indices match again)

	#!ROBOMERGE-SOURCE: CL 3449051 in //Orion/Release-40.1/... via CL 3449332 via CL 3449348
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3455559 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Fix tag issue. FName comparison on instances FName("A") not consistent between platforms due to static init order. Sorting should be done on the full tag name, which is unique for the gameplay tag system. (Vs the simple tag, which are the "subtags" which are not unique. End result is a bunch of comparisons on FName("A") instances not being the same between platforms).

	#!rb none
	@Andrew.Grant
	#!tests PS4 + Dedicated server (verified tag indices match again)

	#!ROBOMERGE-SOURCE: CL 3449051 in //Orion/Release-40.1/... via CL 3449332 via CL 3449348
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3455558 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Fix tag issue. FName comparison on instances FName("A") not consistent between platforms due to static init order. Sorting should be done on the full tag name, which is unique for the gameplay tag system. (Vs the simple tag, which are the "subtags" which are not unique. End result is a bunch of comparisons on FName("A") instances not being the same between platforms).

	#!rb none
	@Andrew.Grant
	#!tests PS4 + Dedicated server (verified tag indices match again)

	#!ROBOMERGE-SOURCE: CL 3449051 in //Orion/Release-40.1/... via CL 3449332 via CL 3449348
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3455555 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Fix tag issue. FName comparison on instances FName("A") not consistent between platforms due to static init order. Sorting should be done on the full tag name, which is unique for the gameplay tag system. (Vs the simple tag, which are the "subtags" which are not unique. End result is a bunch of comparisons on FName("A") instances not being the same between platforms).

	#!rb none
	@Andrew.Grant
	#!tests PS4 + Dedicated server (verified tag indices match again)

	#!ROBOMERGE-SOURCE: CL 3449051 in //Orion/Release-40.1/... via CL 3449332 via CL 3449348
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3455554 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Fix tag issue. FName comparison on instances FName("A") not consistent between platforms due to static init order. Sorting should be done on the full tag name, which is unique for the gameplay tag system. (Vs the simple tag, which are the "subtags" which are not unique. End result is a bunch of comparisons on FName("A") instances not being the same between platforms).

	#!rb none
	@Andrew.Grant
	#!tests PS4 + Dedicated server (verified tag indices match again)

	#!ROBOMERGE-SOURCE: CL 3449051 in //Orion/Release-40.1/... via CL 3449332 via CL 3449348
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3455543 on 2017/05/23 by andrew.grant

	Switch obj list forget and obj list remember to use FObjectKey for comparisons
	#!rb David.Ratti
	#!tests ran forget / remember commands in frontend

	#!ROBOMERGE-SOURCE: CL 3448662 in //Orion/Release-40.1/... via CL 3449329 via CL 3449345
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

	#!ROBOMERGE-SAYS: Unresolved conflicts. andrew.grant, please merge this change by hand.
	//ROBOMERGE_ORION_Dev_General/Engine/Source/Runtime/Engine/Private/UnrealEngine.cpp
	#!CodeReview: andrew.grant, jason.bestimt, jeff.williams

Change 3455281 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Switch obj list forget and obj list remember to use FObjectKey for comparisons
	#!rb David.Ratti
	#!tests ran forget / remember commands in frontend

	#!ROBOMERGE-SOURCE: CL 3448662 in //Orion/Release-40.1/... via CL 3449329 via CL 3449345
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3455280 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Switch obj list forget and obj list remember to use FObjectKey for comparisons
	#!rb David.Ratti
	#!tests ran forget / remember commands in frontend

	#!ROBOMERGE-SOURCE: CL 3448662 in //Orion/Release-40.1/... via CL 3449329 via CL 3449345
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3455279 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Switch obj list forget and obj list remember to use FObjectKey for comparisons
	#!rb David.Ratti
	#!tests ran forget / remember commands in frontend

	#!ROBOMERGE-SOURCE: CL 3448662 in //Orion/Release-40.1/... via CL 3449329 via CL 3449345
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3455278 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Switch obj list forget and obj list remember to use FObjectKey for comparisons
	#!rb David.Ratti
	#!tests ran forget / remember commands in frontend

	#!ROBOMERGE-SOURCE: CL 3448662 in //Orion/Release-40.1/... via CL 3449329 via CL 3449345
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3455256 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - display duration stats at the end of a test
	#!rb none
	#!tests - ran tests

	#!ROBOMERGE-SOURCE: CL 3447866 in //Orion/Release-40.1/... via CL 3449323 via CL 3449340
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3455255 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - display duration stats at the end of a test
	#!rb none
	#!tests - ran tests

	#!ROBOMERGE-SOURCE: CL 3447866 in //Orion/Release-40.1/... via CL 3449323 via CL 3449340
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3455254 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - display duration stats at the end of a test
	#!rb none
	#!tests - ran tests

	#!ROBOMERGE-SOURCE: CL 3447866 in //Orion/Release-40.1/... via CL 3449323 via CL 3449340
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3455253 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - display duration stats at the end of a test
	#!rb none
	#!tests - ran tests

	#!ROBOMERGE-SOURCE: CL 3447866 in //Orion/Release-40.1/... via CL 3449323 via CL 3449340
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3455252 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - display duration stats at the end of a test
	#!rb none
	#!tests - ran tests

	#!ROBOMERGE-SOURCE: CL 3447866 in //Orion/Release-40.1/... via CL 3449323 via CL 3449340
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3455246 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	- Added stats about loaded MCP items while reporting memory heartbeat for post-mortem analysis
	- Run a Trim() while switching loading mode (may help with OOMs while transitioning from draft -> game)

	#!tests ran soak locally
	#!rb none
	@David.Ratti, @Daniel.Lamb

	#!ROBOMERGE-SOURCE: CL 3447863 in //Orion/Release-40.1/... via CL 3449321 via CL 3449338
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3455245 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	- Added stats about loaded MCP items while reporting memory heartbeat for post-mortem analysis
	- Run a Trim() while switching loading mode (may help with OOMs while transitioning from draft -> game)

	#!tests ran soak locally
	#!rb none
	@David.Ratti, @Daniel.Lamb

	#!ROBOMERGE-SOURCE: CL 3447863 in //Orion/Release-40.1/... via CL 3449321 via CL 3449338
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3455244 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	- Added stats about loaded MCP items while reporting memory heartbeat for post-mortem analysis
	- Run a Trim() while switching loading mode (may help with OOMs while transitioning from draft -> game)

	#!tests ran soak locally
	#!rb none
	@David.Ratti, @Daniel.Lamb

	#!ROBOMERGE-SOURCE: CL 3447863 in //Orion/Release-40.1/... via CL 3449321 via CL 3449338
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3455243 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	- Added stats about loaded MCP items while reporting memory heartbeat for post-mortem analysis
	- Run a Trim() while switching loading mode (may help with OOMs while transitioning from draft -> game)

	#!tests ran soak locally
	#!rb none
	@David.Ratti, @Daniel.Lamb

	#!ROBOMERGE-SOURCE: CL 3447863 in //Orion/Release-40.1/... via CL 3449321 via CL 3449338
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3455242 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	- Added stats about loaded MCP items while reporting memory heartbeat for post-mortem analysis
	- Run a Trim() while switching loading mode (may help with OOMs while transitioning from draft -> game)

	#!tests ran soak locally
	#!rb none
	@David.Ratti, @Daniel.Lamb

	#!ROBOMERGE-SOURCE: CL 3447863 in //Orion/Release-40.1/... via CL 3449321 via CL 3449338
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3455227 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added "obj list forget" to exclude all current objects from future "obj list" reports. This allows all current objects to be excluded when trying to track leaks, object ownership etc.

	"obj list remember" resets that list

	#!rb none
	#!tests verified after "obj list forget" only new objects are reported
	@David.Ratti, @Michael.Noland

	#!ROBOMERGE-SOURCE: CL 3447574 in //Orion/Release-40.1/... via CL 3449317 via CL 3449335
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3455223 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added "obj list forget" to exclude all current objects from future "obj list" reports. This allows all current objects to be excluded when trying to track leaks, object ownership etc.

	"obj list remember" resets that list

	#!rb none
	#!tests verified after "obj list forget" only new objects are reported
	@David.Ratti, @Michael.Noland

	#!ROBOMERGE-SOURCE: CL 3447574 in //Orion/Release-40.1/... via CL 3449317 via CL 3449335
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3455222 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added "obj list forget" to exclude all current objects from future "obj list" reports. This allows all current objects to be excluded when trying to track leaks, object ownership etc.

	"obj list remember" resets that list

	#!rb none
	#!tests verified after "obj list forget" only new objects are reported
	@David.Ratti, @Michael.Noland

	#!ROBOMERGE-SOURCE: CL 3447574 in //Orion/Release-40.1/... via CL 3449317 via CL 3449335
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3455221 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added "obj list forget" to exclude all current objects from future "obj list" reports. This allows all current objects to be excluded when trying to track leaks, object ownership etc.

	"obj list remember" resets that list

	#!rb none
	#!tests verified after "obj list forget" only new objects are reported
	@David.Ratti, @Michael.Noland

	#!ROBOMERGE-SOURCE: CL 3447574 in //Orion/Release-40.1/... via CL 3449317 via CL 3449335
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3455218 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added "obj list forget" to exclude all current objects from future "obj list" reports. This allows all current objects to be excluded when trying to track leaks, object ownership etc.

	"obj list remember" resets that list

	#!rb none
	#!tests verified after "obj list forget" only new objects are reported
	@David.Ratti, @Michael.Noland

	#!ROBOMERGE-SOURCE: CL 3447574 in //Orion/Release-40.1/... via CL 3449317 via CL 3449335
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3455141 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Exposing BP write access to UPrimitiveComponent::bSingleSampleShadowFromStationaryLights for Jordan

	#!rb none
	#!tests compile
	[CODEREVIEW] Daniel.Wright

	#!ROBOMERGE-SOURCE: CL 3449046 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3455138 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Exposing BP write access to UPrimitiveComponent::bSingleSampleShadowFromStationaryLights for Jordan

	#!rb none
	#!tests compile
	[CODEREVIEW] Daniel.Wright

	#!ROBOMERGE-SOURCE: CL 3449046 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3455137 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Exposing BP write access to UPrimitiveComponent::bSingleSampleShadowFromStationaryLights for Jordan

	#!rb none
	#!tests compile
	[CODEREVIEW] Daniel.Wright

	#!ROBOMERGE-SOURCE: CL 3449046 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3455136 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Exposing BP write access to UPrimitiveComponent::bSingleSampleShadowFromStationaryLights for Jordan

	#!rb none
	#!tests compile
	[CODEREVIEW] Daniel.Wright

	#!ROBOMERGE-SOURCE: CL 3449046 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3455135 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: dan.hertzka
	Exposing BP write access to UPrimitiveComponent::bSingleSampleShadowFromStationaryLights for Jordan

	#!rb none
	#!tests compile
	[CODEREVIEW] Daniel.Wright

	#!ROBOMERGE-SOURCE: CL 3449046 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3454889 on 2017/05/23 by Laurent.Delayen

	Added missing checks from CL #!1885745, to ensure parents are before children in RefSkeleton.

	#!rb lina.halper
	#!codereview martin.wilson
	#!tests  Ghost PIE

Change 3454884 on 2017/05/23 by Laurent.Delayen

	Minor optimization to FAnimationRuntime::CreateMaskWeights. Since Parents are before Children, use that to speed up Mask Weight creation.

	#!rb lina.halper
	#!codereview thomas.sarkanen
	#!tests  Ghost PIE

Change 3454882 on 2017/05/23 by Laurent.Delayen

	Minor refactor to AnimNode_LayeredBoneBlend.

	#!rb lina.halper
	#!tests  Ghost PIE

Change 3454876 on 2017/05/23 by Don.Eubanks

	Added "Focusable?" column to Widget Reflector, to help provide a jumping off point for tracking down potential issues with Slate focusability.  Hopefully this can help cut down on the arduous "WHY ISN'T THIS BEING FOCUSED" investigations that require Debug Editor and breakpoint voodoo.

	#!rb dan.hertzka
	#!review-3454877 @nick.darnell
	#!test Verified that Widget Reflector shows correct data in Focused? category, and that the data is correctly preserved when taking snapshots and saving/loading snapshots from disk across separate editor sessions.

Change 3454865 on 2017/05/23 by Shaun.Kime

	Catchall secondary integration from Orion\Dev-General to Dev-Niagara
	#!rb none
	#!tests ran normal tests
	#!lockdown Andrew.Grant

Change 3454822 on 2017/05/23 by Shaun.Kime

	Integrating from Orion\Dev-General to Dev-Niagara

	#!rb none
	#!tests opened all existing niagara assets and made sure that they still ran
	#!lockdown Andrew.Grant

Change 3454733 on 2017/05/23 by David.Ratti

	Orion: PIP attribute custom calculation classes

	Ability system: added FinalCurveLookup property to FCustomCalculationBasedFloat. This allows the output of the custom calc class (and pre/post adds) to be a lookup in a table rather than a raw value. Similiar to the table lookup that attribute based calculations support.

	#!rb lietz
	#!tests pie
	#!review-3454734 @Billy.Bramer, @Fred.Kimberley

Change 3454524 on 2017/05/23 by David.Ratti

	Support for generic FlatAdditive attribute channel: this is an extra channel that only allows additive mods on it. For doing things like "Flat Mana regen".

	#!rb Lietz
	#!tests PIE
	#!review-3454525 @Billy.Bramer

Change 3454462 on 2017/05/23 by Daniel.Lamb

	Potential fix for asset registry deterministic hash generation.
	#!rb Ben.Zeigler
	#!test Compile run editor

Change 3454042 on 2017/05/23 by Don.Eubanks

	Added accessor for FSlateApplication::NavigationConfig as I need to dynamically swap it in and out for this specific screen.

	#!rb phil.buuck
	#!review-3454043 @nick.darnell @nick.atamas
	#!tests Compiled Win64 / PS4

Change 3454019 on 2017/05/23 by Shaun.Kime

	Changed the signature of BuildParameterMapHistory so that we can build parameter maps even when there is no parameter map on the output pin. This was needed for Frank's DynamicInputs.

	Modified NiagaraNodeEmitter to allow you to override pins.


	#!rb none
	#!codereview frank.fella
	#!tests checked against all known example assets

Change 3453915 on 2017/05/23 by David.Ratti

	remove some logspam that was added to track down linux server issue

	#!rb none
	#!tests compile

Change 3453846 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Attempting to fix https://jira.it.epicgames.net/browse/OR-38702
	Added fallback in case we were not able to successfully CacheData, which could leave us with bad data.
	Added checks to make sure we're not getting bad data into core functions.

	[CODEREVIEW] lina.halper
	#!rb none
	#!tests Phase, Ice 2 client network game.

	#!ROBOMERGE-SOURCE: CL 3447278 in //Orion/Release-40.2/... via CL 3447281
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3453845 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Attempting to fix https://jira.it.epicgames.net/browse/OR-38702
	Added fallback in case we were not able to successfully CacheData, which could leave us with bad data.
	Added checks to make sure we're not getting bad data into core functions.

	[CODEREVIEW] lina.halper
	#!rb none
	#!tests Phase, Ice 2 client network game.

	#!ROBOMERGE-SOURCE: CL 3447278 in //Orion/Release-40.2/... via CL 3447281
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3453842 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Attempting to fix https://jira.it.epicgames.net/browse/OR-38702
	Added fallback in case we were not able to successfully CacheData, which could leave us with bad data.
	Added checks to make sure we're not getting bad data into core functions.

	[CODEREVIEW] lina.halper
	#!rb none
	#!tests Phase, Ice 2 client network game.

	#!ROBOMERGE-SOURCE: CL 3447278 in //Orion/Release-40.2/... via CL 3447281
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3453841 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Attempting to fix https://jira.it.epicgames.net/browse/OR-38702
	Added fallback in case we were not able to successfully CacheData, which could leave us with bad data.
	Added checks to make sure we're not getting bad data into core functions.

	[CODEREVIEW] lina.halper
	#!rb none
	#!tests Phase, Ice 2 client network game.

	#!ROBOMERGE-SOURCE: CL 3447278 in //Orion/Release-40.2/... via CL 3447281
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3453840 on 2017/05/23 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Attempting to fix https://jira.it.epicgames.net/browse/OR-38702
	Added fallback in case we were not able to successfully CacheData, which could leave us with bad data.
	Added checks to make sure we're not getting bad data into core functions.

	[CODEREVIEW] lina.halper
	#!rb none
	#!tests Phase, Ice 2 client network game.

	#!ROBOMERGE-SOURCE: CL 3447278 in //Orion/Release-40.2/... via CL 3447281
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3453819 on 2017/05/23 by Mieszko.Zielinski

	Fixes to BotScriptedBehaviors are being run and how Bot AFK behavior is implemented #!Orion

	Switched AFK behavior from overriding the whole BT to using scripted behaviors, which surfaced some bugs that this CL is fixing.

	Related to jira OR-38537

	Manually resolved conflicts robomerge was complaining about

	#!rb none
	#!test golden path

	#!ROBOMERGE-SOURCE: CL 3447169 in //Orion/Release-40.2/... via CL 3447170
	ORION (Main -> Dev-General)

	#!CodeReview: jason.bestimt, andrew.grant, jeff.williams

Change 3453150 on 2017/05/22 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Fixes to BotScriptedBehaviors are being run and how Bot AFK behavior is implemented #!Orion

	Switched AFK behavior from overriding the whole BT to using scripted behaviors, which surfaced some bugs that this CL is fixing.

	Related to jira OR-38537

	#!rb none
	#!test golden path

	#!ROBOMERGE-SOURCE: CL 3447169 in //Orion/Release-40.2/... via CL 3447170
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3453149 on 2017/05/22 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Fixes to BotScriptedBehaviors are being run and how Bot AFK behavior is implemented #!Orion

	Switched AFK behavior from overriding the whole BT to using scripted behaviors, which surfaced some bugs that this CL is fixing.

	Related to jira OR-38537

	#!rb none
	#!test golden path

	#!ROBOMERGE-SOURCE: CL 3447169 in //Orion/Release-40.2/... via CL 3447170
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3453147 on 2017/05/22 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Fixes to BotScriptedBehaviors are being run and how Bot AFK behavior is implemented #!Orion

	Switched AFK behavior from overriding the whole BT to using scripted behaviors, which surfaced some bugs that this CL is fixing.

	Related to jira OR-38537

	#!rb none
	#!test golden path

	#!ROBOMERGE-SOURCE: CL 3447169 in //Orion/Release-40.2/... via CL 3447170
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3453144 on 2017/05/22 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Fixes to BotScriptedBehaviors are being run and how Bot AFK behavior is implemented #!Orion

	Switched AFK behavior from overriding the whole BT to using scripted behaviors, which surfaced some bugs that this CL is fixing.

	Related to jira OR-38537

	#!rb none
	#!test golden path

	#!ROBOMERGE-SOURCE: CL 3447169 in //Orion/Release-40.2/... via CL 3447170
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3452484 on 2017/05/22 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Tweaked MemoryReport test
	- Always dump a memreport on a state change (very useful for comparing two builds)
	- Only dump leak/alloc reports if > 1m into the game (While notimeouts stops the game disconnecting, draft and moba games don't do well if the client is non-responsive).

	#!tests ran MemReport test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3452458 in //Orion/Release-40.1/... via CL 3452461
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3452461 on 2017/05/22 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Tweaked MemoryReport test
	- Always dump a memreport on a state change (very useful for comparing two builds)
	- Only dump leak/alloc reports if > 1m into the game (While notimeouts stops the game disconnecting, draft and moba games don't do well if the client is non-responsive).

	#!tests ran MemReport test locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3452458 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3452458 on 2017/05/22 by Andrew.Grant

	Tweaked MemoryReport test
	- Always dump a memreport on a state change (very useful for comparing two builds)
	- Only dump leak/alloc reports if > 1m into the game (While notimeouts stops the game disconnecting, draft and moba games don't do well if the client is non-responsive).

	#!tests ran MemReport test locally
	#!rb none

Change 3452042 on 2017/05/22 by Matt.Kuhlenschmidt

	Exposing more niagara types to details panel

	#!codereview frank.fella
	#!rb shaun.kime
	#!tests none

Change 3451912 on 2017/05/22 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed typo in obj command (non-shipping change
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3451906 in //Orion/Release-40.1/... via CL 3451908
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3451908 on 2017/05/22 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fixed typo in obj command (non-shipping change
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3451906 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3451906 on 2017/05/22 by Andrew.Grant

	Fixed typo in obj command (non-shipping change
	#!tests #!rb none

Change 3451835 on 2017/05/22 by Philip.Buuck

	Potential fix for fonts not loading in cooked, prevent font cache from constantly reloading font.

	#!rb none (shelved by Jamie.Dale)
	#!tests PIE
	#!review-3451837 Matt.Schembari, Dan.Hertzka, Don.Eubanks

Change 3451832 on 2017/05/22 by Daniel.Lamb

	Fixed issue with reflection captures not refreshing correctly in resavepackages commandlet.
	#!rb Daniel.Wright
	#!test Resave packages commandlet with allow commandlet rendering.

Change 3449936 on 2017/05/19 by Andrew.Grant

	Removing super-spammy post-merge warning.
	#!tests compiled
	#!rb none

Change 3449829 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Allow branch & CL to be passed into Gauntlet for reporting
	Pass branch and CL in to Gauntlet for editor tests so logs end up under branch folder
	#!tests ran editor tests locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3449827 in //Orion/Release-40.1/... via CL 3449828
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3449828 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Allow branch & CL to be passed into Gauntlet for reporting
	Pass branch and CL in to Gauntlet for editor tests so logs end up under branch folder
	#!tests ran editor tests locally
	#!rb none

	#!ROBOMERGE-SOURCE: CL 3449827 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3449827 on 2017/05/19 by Andrew.Grant

	Allow branch & CL to be passed into Gauntlet for reporting
	Pass branch and CL in to Gauntlet for editor tests so logs end up under branch folder
	#!tests ran editor tests locally
	#!rb none

Change 3449759 on 2017/05/19 by Andrew.Grant

	Merging //UE4/Main @ 3441199 through //UE4/Orion-Staging
	#!tests QA pass
	#!rb none

Change 3449606 on 2017/05/19 by Dan.Hertzka

	Properly exposing bSingleSampleShadowFromStationaryLights to BP

	#!codereview Daniel.Wright
	#!rb none
	#!tests compile

Change 3449518 on 2017/05/19 by Frank.Fella

	Niagara - Stack - Fixes
	+ StackScriptItemGroup - Fix the code which traverses the graph so that it only returns actual modules instead of every function call.  This prevents trying to generate module items for dynamic input function calls.
	+ StackEntry - Don't force generating children when initializing the colors.

	#!Tests no longer ensures and crashes when opening an emitter with test dynamic inputs.
	#!rb none

Change 3449474 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Gauntlet file copy to use parallel-for with 2 threads. Takes deploy time down from ~14m to 11m

	#!rb none
	@Daniel.Lamb
	#!tests deployed locally staged and network builds

	#!ROBOMERGE-SOURCE: CL 3449370 in //Orion/Release-40.1/... via CL 3449372
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3449372 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Changed Gauntlet file copy to use parallel-for with 2 threads. Takes deploy time down from ~14m to 11m

	#!rb none
	@Daniel.Lamb
	#!tests deployed locally staged and network builds

	#!ROBOMERGE-SOURCE: CL 3449370 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3449370 on 2017/05/19 by Andrew.Grant

	Changed Gauntlet file copy to use parallel-for with 2 threads. Takes deploy time down from ~14m to 11m

	#!rb none
	#!review-3449371 @Daniel.Lamb
	#!tests deployed locally staged and network builds

Change 3449348 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Fix tag issue. FName comparison on instances FName("A") not consistent between platforms due to static init order. Sorting should be done on the full tag name, which is unique for the gameplay tag system. (Vs the simple tag, which are the "subtags" which are not unique. End result is a bunch of comparisons on FName("A") instances not being the same between platforms).

	#!rb none
	@Andrew.Grant
	#!tests PS4 + Dedicated server (verified tag indices match again)

	#!ROBOMERGE-SOURCE: CL 3449051 in //Orion/Release-40.1/... via CL 3449332
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3449345 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Switch obj list forget and obj list remember to use FObjectKey for comparisons
	#!rb David.Ratti
	#!tests ran forget / remember commands in frontend

	#!ROBOMERGE-SOURCE: CL 3448662 in //Orion/Release-40.1/... via CL 3449329
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3449340 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - display duration stats at the end of a test
	#!rb none
	#!tests - ran tests

	#!ROBOMERGE-SOURCE: CL 3447866 in //Orion/Release-40.1/... via CL 3449323
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3449338 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	- Added stats about loaded MCP items while reporting memory heartbeat for post-mortem analysis
	- Run a Trim() while switching loading mode (may help with OOMs while transitioning from draft -> game)

	#!tests ran soak locally
	#!rb none
	@David.Ratti, @Daniel.Lamb

	#!ROBOMERGE-SOURCE: CL 3447863 in //Orion/Release-40.1/... via CL 3449321
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3449335 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added "obj list forget" to exclude all current objects from future "obj list" reports. This allows all current objects to be excluded when trying to track leaks, object ownership etc.

	"obj list remember" resets that list

	#!rb none
	#!tests verified after "obj list forget" only new objects are reported
	@David.Ratti, @Michael.Noland

	#!ROBOMERGE-SOURCE: CL 3447574 in //Orion/Release-40.1/... via CL 3449317
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3449332 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: david.ratti
	Fix tag issue. FName comparison on instances FName("A") not consistent between platforms due to static init order. Sorting should be done on the full tag name, which is unique for the gameplay tag system. (Vs the simple tag, which are the "subtags" which are not unique. End result is a bunch of comparisons on FName("A") instances not being the same between platforms).

	#!rb none
	@Andrew.Grant
	#!tests PS4 + Dedicated server (verified tag indices match again)

	#!ROBOMERGE-SOURCE: CL 3449051 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3449329 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Switch obj list forget and obj list remember to use FObjectKey for comparisons
	#!rb David.Ratti
	#!tests ran forget / remember commands in frontend

	#!ROBOMERGE-SOURCE: CL 3448662 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3449323 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Gauntlet - display duration stats at the end of a test
	#!rb none
	#!tests - ran tests

	#!ROBOMERGE-SOURCE: CL 3447866 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3449321 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	- Added stats about loaded MCP items while reporting memory heartbeat for post-mortem analysis
	- Run a Trim() while switching loading mode (may help with OOMs while transitioning from draft -> game)

	#!tests ran soak locally
	#!rb none
	@David.Ratti, @Daniel.Lamb

	#!ROBOMERGE-SOURCE: CL 3447863 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3449317 on 2017/05/19 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Added "obj list forget" to exclude all current objects from future "obj list" reports. This allows all current objects to be excluded when trying to track leaks, object ownership etc.

	"obj list remember" resets that list

	#!rb none
	#!tests verified after "obj list forget" only new objects are reported
	@David.Ratti, @Michael.Noland

	#!ROBOMERGE-SOURCE: CL 3447574 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3449152 on 2017/05/19 by Andrew.Grant

	3440740 from DG
	#!tests #!rb none

Change 3449051 on 2017/05/19 by David.Ratti

	Fix tag issue. FName comparison on instances FName("A") not consistent between platforms due to static init order. Sorting should be done on the full tag name, which is unique for the gameplay tag system. (Vs the simple tag, which are the "subtags" which are not unique. End result is a bunch of comparisons on FName("A") instances not being the same between platforms).

	#!rb none
	#!review-3449052 @Andrew.Grant
	#!tests PS4 + Dedicated server (verified tag indices match again)

Change 3449046 on 2017/05/19 by Dan.Hertzka

	Exposing BP write access to UPrimitiveComponent::bSingleSampleShadowFromStationaryLights for Jordan

	#!rb none
	#!tests compile
	#!codereview Daniel.Wright

Change 3449009 on 2017/05/19 by Shaun.Kime

	Now using the Instance.Alive parameter to decide whether or not we kill the particle rather than doing it entirely on the CPU in PostProcessParticles.

	Created KillOnCollision and GenerateEventOnDeath modules.

	Currently the VM crashes writing to an int32 in the spawn script if you add a KillOnCollision module to the end of BouncableFountain.uasset.

	#!rb none
	#!tests recompiled all the known emitters
	#!code.review olaf.piesche

Change 3448662 on 2017/05/19 by Andrew.Grant

	Switch obj list forget and obj list remember to use FObjectKey for comparisons
	#!rb David.Ratti
	#!tests ran forget / remember commands in frontend

Change 3447866 on 2017/05/18 by Andrew.Grant

	Gauntlet - display duration stats at the end of a test
	#!rb none
	#!tests - ran tests

Change 3447863 on 2017/05/18 by Andrew.Grant

	- Added stats about loaded MCP items while reporting memory heartbeat for post-mortem analysis
	- Run a Trim() while switching loading mode (may help with OOMs while transitioning from draft -> game)

	#!tests ran soak locally
	#!rb none
	#!review-3447864 @David.Ratti, @Daniel.Lamb

Change 3447574 on 2017/05/18 by Andrew.Grant

	Added "obj list forget" to exclude all current objects from future "obj list" reports. This allows all current objects to be excluded when trying to track leaks, object ownership etc.

	"obj list remember" resets that list

	#!rb none
	#!tests verified after "obj list forget" only new objects are reported
	#!review-3447575 @David.Ratti, @Michael.Noland

Change 3447281 on 2017/05/18 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Attempting to fix https://jira.it.epicgames.net/browse/OR-38702
	Added fallback in case we were not able to successfully CacheData, which could leave us with bad data.
	Added checks to make sure we're not getting bad data into core functions.

	[CODEREVIEW] lina.halper
	#!rb none
	#!tests Phase, Ice 2 client network game.

	#!ROBOMERGE-SOURCE: CL 3447278 in //Orion/Release-40.2/...
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3447278 on 2017/05/18 by Laurent.Delayen

	Attempting to fix https://jira.it.epicgames.net/browse/OR-38702
	Added fallback in case we were not able to successfully CacheData, which could leave us with bad data.
	Added checks to make sure we're not getting bad data into core functions.

	#!codereview lina.halper
	#!rb none
	#!tests Phase, Ice 2 client network game.

Change 3447170 on 2017/05/18 by robomerge

	#!ROBOMERGE-AUTHOR: mieszko.zielinski
	Fixes to BotScriptedBehaviors are being run and how Bot AFK behavior is implemented #!Orion

	Switched AFK behavior from overriding the whole BT to using scripted behaviors, which surfaced some bugs that this CL is fixing.

	Related to jira OR-38537

	#!rb none
	#!test golden path

	#!ROBOMERGE-SOURCE: CL 3447169 in //Orion/Release-40.2/...
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3447169 on 2017/05/18 by Mieszko.Zielinski

	Fixes to BotScriptedBehaviors are being run and how Bot AFK behavior is implemented #!Orion

	Switched AFK behavior from overriding the whole BT to using scripted behaviors, which surfaced some bugs that this CL is fixing.

	Related to jira OR-38537

	#!rb none
	#!test golden path

Change 3447072 on 2017/05/18 by Frank.Fella

	Niagara - Spacebar now resets the simulation as long as you don't have the sequencer timeline focused, also starting and stopping the simulation with sequencer no longer resets the system 50% of the time.

	#!tests Verified the issues above were fixed.
	#!rb none

	#!codereview Olaf.Piesche,Simon.Tovey,Shaun.Kime

Change 3446668 on 2017/05/18 by Shaun.Kime

	Removed the previous way of setting module defaults and instead moved to a method where the get node is allowed to specify the defaults all on its own.

	Tested adding a default curve to AnimatedDynamicMaterialParameter and it properly animates until the user overrides it, see FunctionalTests/DefaultCurve

	#!rb none
	#!codereview simon.tovey, frank.fella, olaf.piesche
	#!tests re-saved all of our existing modules and reviewed sample emitters.

Change 3446043 on 2017/05/18 by Jurre.deBaare

	Issue with hitches when vertex painting
	#!fix FStaticMeshComponentRecreateRenderStateContext was incorrectly scoped/used
	#!misc add preventive check for invalid vertex buffer
	#!codereview Andrew.Grant
	#!rb none
	#!tests painted pointed out meshes by PatJ in Astrolabe

Change 3444712 on 2017/05/17 by Frank.Fella

	Niagara - Stack - Add module outputs

	#!tests Module stack items now have a read-only section for their outputs
	#!rb none

	#!codereview Olaf.Piesche,Simon.Tovey,Shaun.Kime

Change 3444672 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed FRootMotionSource_JumpForce not maintaining velocity on the last tick. TimeFractions were not correctly adjusted when going over Duration, resulting in reduced velocity applied, sometimes really close to zero.

	Fixes Wukong double jump sometimes looking like it's hitting a wall.

	[CODEREVIEW] frank.gigliotti
	#!rb none
	#!tests wukong double jump

	#!ROBOMERGE-SOURCE: CL 3444666 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3444671 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed FRootMotionSource_JumpForce not maintaining velocity on the last tick. TimeFractions were not correctly adjusted when going over Duration, resulting in reduced velocity applied, sometimes really close to zero.

	Fixes Wukong double jump sometimes looking like it's hitting a wall.

	[CODEREVIEW] frank.gigliotti
	#!rb none
	#!tests wukong double jump

	#!ROBOMERGE-SOURCE: CL 3444666 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3444670 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed FRootMotionSource_JumpForce not maintaining velocity on the last tick. TimeFractions were not correctly adjusted when going over Duration, resulting in reduced velocity applied, sometimes really close to zero.

	Fixes Wukong double jump sometimes looking like it's hitting a wall.

	[CODEREVIEW] frank.gigliotti
	#!rb none
	#!tests wukong double jump

	#!ROBOMERGE-SOURCE: CL 3444666 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3444669 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed FRootMotionSource_JumpForce not maintaining velocity on the last tick. TimeFractions were not correctly adjusted when going over Duration, resulting in reduced velocity applied, sometimes really close to zero.

	Fixes Wukong double jump sometimes looking like it's hitting a wall.

	[CODEREVIEW] frank.gigliotti
	#!rb none
	#!tests wukong double jump

	#!ROBOMERGE-SOURCE: CL 3444666 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3444668 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed FRootMotionSource_JumpForce not maintaining velocity on the last tick. TimeFractions were not correctly adjusted when going over Duration, resulting in reduced velocity applied, sometimes really close to zero.

	Fixes Wukong double jump sometimes looking like it's hitting a wall.

	[CODEREVIEW] frank.gigliotti
	#!rb none
	#!tests wukong double jump

	#!ROBOMERGE-SOURCE: CL 3444666 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3444666 on 2017/05/17 by Laurent.Delayen

	Fixed FRootMotionSource_JumpForce not maintaining velocity on the last tick. TimeFractions were not correctly adjusted when going over Duration, resulting in reduced velocity applied, sometimes really close to zero.

	Fixes Wukong double jump sometimes looking like it's hitting a wall.

	#!codereview frank.gigliotti
	#!rb none
	#!tests wukong double jump

Change 3444525 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fix for OR-38662 (Invalid Table warning)
	#!rb David.Ratti
	#!tests verified warning is gone

	#!ROBOMERGE-SOURCE: CL 3443023 in //Orion/Release-40.1/... via CL 3443024 via CL 3443025
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3444524 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fix for OR-38662 (Invalid Table warning)
	#!rb David.Ratti
	#!tests verified warning is gone

	#!ROBOMERGE-SOURCE: CL 3443023 in //Orion/Release-40.1/... via CL 3443024 via CL 3443025
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3444523 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fix for OR-38662 (Invalid Table warning)
	#!rb David.Ratti
	#!tests verified warning is gone

	#!ROBOMERGE-SOURCE: CL 3443023 in //Orion/Release-40.1/... via CL 3443024 via CL 3443025
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3444522 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fix for OR-38662 (Invalid Table warning)
	#!rb David.Ratti
	#!tests verified warning is gone

	#!ROBOMERGE-SOURCE: CL 3443023 in //Orion/Release-40.1/... via CL 3443024 via CL 3443025
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3444521 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fix for OR-38662 (Invalid Table warning)
	#!rb David.Ratti
	#!tests verified warning is gone

	#!ROBOMERGE-SOURCE: CL 3443023 in //Orion/Release-40.1/... via CL 3443024 via CL 3443025
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3443073 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Added different methods for scaling chain in AnimNode_ScaleChainLength. Based on chain length, or distance between end points. Also exposed Alpha to Display Debug.

	#!rb none
	#!tests wukong RMB

	#!ROBOMERGE-SOURCE: CL 3441628 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3443072 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Added different methods for scaling chain in AnimNode_ScaleChainLength. Based on chain length, or distance between end points. Also exposed Alpha to Display Debug.

	#!rb none
	#!tests wukong RMB

	#!ROBOMERGE-SOURCE: CL 3441628 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3443071 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Added different methods for scaling chain in AnimNode_ScaleChainLength. Based on chain length, or distance between end points. Also exposed Alpha to Display Debug.

	#!rb none
	#!tests wukong RMB

	#!ROBOMERGE-SOURCE: CL 3441628 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3443070 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Added different methods for scaling chain in AnimNode_ScaleChainLength. Based on chain length, or distance between end points. Also exposed Alpha to Display Debug.

	#!rb none
	#!tests wukong RMB

	#!ROBOMERGE-SOURCE: CL 3441628 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3443068 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Added different methods for scaling chain in AnimNode_ScaleChainLength. Based on chain length, or distance between end points. Also exposed Alpha to Display Debug.

	#!rb none
	#!tests wukong RMB

	#!ROBOMERGE-SOURCE: CL 3441628 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3443025 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fix for OR-38662 (Invalid Table warning)
	#!rb David.Ratti
	#!tests verified warning is gone

	#!ROBOMERGE-SOURCE: CL 3443023 in //Orion/Release-40.1/... via CL 3443024
	#!ROBOMERGE-BOT: ORION (Release-40.2 -> Main)

Change 3443024 on 2017/05/17 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Fix for OR-38662 (Invalid Table warning)
	#!rb David.Ratti
	#!tests verified warning is gone

	#!ROBOMERGE-SOURCE: CL 3443023 in //Orion/Release-40.1/...
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Release-40.2)

Change 3443023 on 2017/05/17 by Andrew.Grant

	Fix for OR-38662 (Invalid Table warning)
	#!rb David.Ratti
	#!tests verified warning is gone

Change 3442508 on 2017/05/16 by Jeff.Williams

	Merging //Orion/Main to Release-40.2 (//Orion/Release-40.2) @3442434

	#!rb none
	#!tests none

Change 3442172 on 2017/05/16 by Jeff.Williams

	Initial branch of files from Release-40.1 (//Orion/Release-40.1) to Release-40.2 (//Orion/Release-40.2)

Change 3441928 on 2017/05/16 by Alexis.Matte

	rephrase fbx re-import preview skeleton warning
	#!rb none
	#!tests none

Change 3441882 on 2017/05/16 by Andrew.Grant

	Integrating UE-44837 from Dev-Editor
	#!tests #!rb none

Change 3441848 on 2017/05/16 by Jeff.Williams

	Initial branch of files from Dev-UI (//Orion/Dev-UI) to Dev-UI-Playtest (//Orion/Dev-UI-Playtest)

Change 3441628 on 2017/05/16 by Laurent.Delayen

	Added different methods for scaling chain in AnimNode_ScaleChainLength. Based on chain length, or distance between end points. Also exposed Alpha to Display Debug.

	#!rb none
	#!tests wukong RMB

Change 3441486 on 2017/05/16 by Simon.Tovey

	Fixed spelling error

	#!rb none
	#!tests none

Change 3441425 on 2017/05/16 by Simon.Tovey

	Second phase of parameter collections.
	Graph node linking to collection and compiling into a script.

	#!codereview Shaun.Kime, Olaf.Piesche, Frank.Fella

	#!tests basics work
	#!rb none

Change 3441422 on 2017/05/16 by Simon.Tovey

	First step of NiagaraParameterCollections

	Asset and editor.
	Currently not used anywhere.

	#!tests Basics work.
	#!rb Shaun.Kime
	#!codereview Shaun.Kime, Frank.Fella, Olaf.Piesche

Change 3441246 on 2017/05/16 by Alexis.Matte

	Remove the alternate color feature in the Detail panel
	#!rb matt.kuhlenschmidt
	#!tests none

Change 3440999 on 2017/05/16 by Andrew.Grant

	Address editor perf by disabling code that was creating temp widget rows.
	#!tests compiled
	#!rb MattK
	#!review-3441000 @alexis.matte

Change 3440874 on 2017/05/16 by Shaun.Kime

	Added ability to create emitter stacks as well as display the event stack in the stack list. We will need to auto-collapse and do some more work to make this manageable in the long run. Added tooltips to each section to help make it clear what it does.

	#!rb none
	#!tests n/a
	#!codereview simon.tovey, frank.fella, olaf.piesce

Change 3440771 on 2017/05/16 by Benn.Gallagher

	Fix for subinstance ensures during re-register operation. We were incorrectly stopping reinitialization after unregister.
	#!rb Martin.Wilson
	#!tests Wukong test level for ensure in PIE + -game

Change 3440740 on 2017/05/16 by David.Ratti

	Fix crash editing tag queries in blueprint defaults
	#!rb none
	#!tests editor

Change 3440308 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed AimOffset's Alpha not getting properly updated during tick. Also added Alpha to display debug.

	#!rb none
	#!tests wukong

	#!ROBOMERGE-SOURCE: CL 3440110 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3440307 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed AimOffset's Alpha not getting properly updated during tick. Also added Alpha to display debug.

	#!rb none
	#!tests wukong

	#!ROBOMERGE-SOURCE: CL 3440110 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3440306 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed AimOffset's Alpha not getting properly updated during tick. Also added Alpha to display debug.

	#!rb none
	#!tests wukong

	#!ROBOMERGE-SOURCE: CL 3440110 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3440305 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed AimOffset's Alpha not getting properly updated during tick. Also added Alpha to display debug.

	#!rb none
	#!tests wukong

	#!ROBOMERGE-SOURCE: CL 3440110 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3440304 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: laurent.delayen
	Fixed AimOffset's Alpha not getting properly updated during tick. Also added Alpha to display debug.

	#!rb none
	#!tests wukong

	#!ROBOMERGE-SOURCE: CL 3440110 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3440255 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Merging 3439766 from //Orion/Dev-UI to Main (fix for tags perf?)
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439864 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3440254 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Merging 3439766 from //Orion/Dev-UI to Main (fix for tags perf?)
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439864 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3440253 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Merging 3439766 from //Orion/Dev-UI to Main (fix for tags perf?)
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439864 in //Orion/Main/...
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3440110 on 2017/05/15 by Laurent.Delayen

	Fixed AimOffset's Alpha not getting properly updated during tick. Also added Alpha to display debug.

	#!rb none
	#!tests wukong

Change 3439885 on 2017/05/15 by Andrew.Grant

	Merging //Orion/Main to Dev-General (//Orion/Dev-General)
	#!tests #!rb none

Change 3439864 on 2017/05/15 by Andrew.Grant

	Merging 3439766 from //Orion/Dev-UI to Main (fix for tags perf?)
	#!tests #!rb none

Change 3439767 on 2017/05/15 by Andrew.Grant

	Defaulting Aftermath to off
	#!tests #!rb none

Change 3439766 on 2017/05/15 by Jon.Lietz

	fixing issue where the OnLastChanceToAddNativeTags() static function was returning a copy of the delegate letting who ever wanted to bind to it only bind to a copy that fell out of scope. fixing it so the function returns a ref to the delegate.

	#!rb none
	#!tests native tags are added and loaded
	#!codereivew david.ratti

Change 3439471 on 2017/05/15 by Shaun.Kime

	Added the ability for each script to specify what other script types can use it, its description, and category. These are all available from the asset registry, making it possible to filter addition of modules in the stack. Necessitated changing this UI to look closer to the graph-based UI for adding modules.

	Changed Spawn and Update scripts to Particle Spawn Script and Particle Update Script. Redirects have been put in place for enum values. Additional enum values were added for emitter and system spawn/update.

	Updated all known modules to have this info now.

	#!rb none
	#!codereview frank.fella, simon.tovey, olaf.piesche
	#!tests opened several existing emitters and made sure that they recompiled successfully

Change 3439217 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked v40 builds to net-cl 3435991
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439208 in //Orion/Release-40/... via CL 3439209 via CL 3439210
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3439216 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked v40 builds to net-cl 3435991
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439208 in //Orion/Release-40/... via CL 3439209 via CL 3439210
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3439215 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked v40 builds to net-cl 3435991
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439208 in //Orion/Release-40/... via CL 3439209 via CL 3439210
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3439212 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked v40 builds to net-cl 3435991
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439208 in //Orion/Release-40/... via CL 3439209 via CL 3439210
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3439211 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked v40 builds to net-cl 3435991
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439208 in //Orion/Release-40/... via CL 3439209 via CL 3439210
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3439210 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked v40 builds to net-cl 3435991
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439208 in //Orion/Release-40/... via CL 3439209
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Main)

Change 3439209 on 2017/05/15 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	[NULL MERGE]
	Locked v40 builds to net-cl 3435991
	#!tests #!rb none

	#!ROBOMERGE-SOURCE: CL 3439208 in //Orion/Release-40/...
	#!ROBOMERGE-BOT: ORION (Release-40 -> Release-40.1)

Change 3439208 on 2017/05/15 by Andrew.Grant

	Locked v40 builds to net-cl 3435991
	#!tests #!rb none
	#!ROBOMERGE: !40.1

Change 3438941 on 2017/05/15 by Alexis.Matte

	Import Preview windows
	Meshes editor UI refactor
	Fbx import options Reset to default
	#!jira UE-42755
	#!jira UE-44149
	#!jira UE-44463
	#!jira UE-38985

	#!rb matt.kuhlenschmidt
	#!tests run fbx automation tests

Change 3437669 on 2017/05/12 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made warning an info
	#!rb none
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3437612 in //Orion/Release-40/... via CL 3437613 via CL 3437614
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Cinematics)

Change 3437668 on 2017/05/12 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made warning an info
	#!rb none
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3437612 in //Orion/Release-40/... via CL 3437613 via CL 3437614
	#!ROBOMERGE-BOT: ORION (Main -> Dev-Balance)

Change 3437667 on 2017/05/12 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made warning an info
	#!rb none
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3437612 in //Orion/Release-40/... via CL 3437613 via CL 3437614
	#!ROBOMERGE-BOT: ORION (Main -> Dev-REGS)

Change 3437666 on 2017/05/12 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made warning an info
	#!rb none
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3437612 in //Orion/Release-40/... via CL 3437613 via CL 3437614
	#!ROBOMERGE-BOT: ORION (Main -> Dev-UI)

Change 3437665 on 2017/05/12 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made warning an info
	#!rb none
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3437612 in //Orion/Release-40/... via CL 3437613 via CL 3437614
	#!ROBOMERGE-BOT: ORION (Main -> Dev-General)

Change 3437614 on 2017/05/12 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made warning an info
	#!rb none
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3437612 in //Orion/Release-40/... via CL 3437613
	#!ROBOMERGE-BOT: ORION (Release-40.1 -> Main)

Change 3437613 on 2017/05/12 by robomerge

	#!ROBOMERGE-AUTHOR: andrew.grant
	Made warning an info
	#!rb none
	#!tests compiled

	#!ROBOMERGE-SOURCE: CL 3437612 in //Orion/Release-40/...
	#!ROBOMERGE-BOT: ORION (Release-40 -> Release-40.1)

Change 3437612 on 2017/05/12 by Andrew.Grant

	Made warning an info
	#!rb none
	#!tests compiled

[CL 3489016 by Andrew Grant in Main branch]
2017-06-14 08:40:01 -04:00

4370 lines
129 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "IPlatformFilePak.h"
#include "HAL/FileManager.h"
#include "Misc/CoreMisc.h"
#include "Misc/CommandLine.h"
#include "Async/AsyncWork.h"
#include "Serialization/MemoryReader.h"
#include "HAL/IConsoleManager.h"
#include "Misc/CoreDelegates.h"
#include "Misc/App.h"
#include "Modules/ModuleManager.h"
#include "Misc/SecureHash.h"
#include "HAL/FileManagerGeneric.h"
#include "HAL/IPlatformFileModule.h"
#include "SignedArchiveReader.h"
#include "Misc/AES.h"
#include "GenericPlatform/GenericPlatformChunkInstall.h"
#include "Async/AsyncFileHandle.h"
#include "Templates/Greater.h"
#include "Serialization/ArchiveProxy.h"
DEFINE_LOG_CATEGORY(LogPakFile);
DEFINE_STAT(STAT_PakFile_Read);
DEFINE_STAT(STAT_PakFile_NumOpenHandles);
TPakChunkHash ComputePakChunkHash(const void* InData, int64 InDataSizeInBytes)
{
#if PAKHASH_USE_CRC
return FCrc::MemCrc32(InData, InDataSizeInBytes);
#else
FSHAHash Hash;
FSHA1::HashBuffer(InData, InDataSizeInBytes, Hash);
return Hash;
#endif
}
#ifndef EXCLUDE_NONPAK_UE_EXTENSIONS
#define EXCLUDE_NONPAK_UE_EXTENSIONS 1 // Use .Build.cs file to disable this if the game relies on accessing loose files
#endif
FFilenameSecurityDelegate& FPakPlatformFile::GetFilenameSecurityDelegate()
{
static FFilenameSecurityDelegate Delegate;
return Delegate;
}
#define USE_PAK_PRECACHE (!IS_PROGRAM && !WITH_EDITOR) // you can turn this off to use the async IO stuff without the precache
/**
* Precaching
*/
const ANSICHAR* FPakPlatformFile::GetPakEncryptionKey()
{
FCoreDelegates::FPakEncryptionKeyDelegate& Delegate = FCoreDelegates::GetPakEncryptionKeyDelegate();
if (Delegate.IsBound())
{
return Delegate.Execute();
}
else
{
return nullptr;
}
}
void FPakPlatformFile::GetPakSigningKeys(FString& OutExponent, FString& OutModulus)
{
FCoreDelegates::FPakSigningKeysDelegate& Delegate = FCoreDelegates::GetPakSigningKeysDelegate();
if (Delegate.IsBound())
{
return Delegate.Execute(OutExponent, OutModulus);
}
}
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("PakCache Sync Decrypts (Uncompressed Path)"), STAT_PakCache_SyncDecrypts, STATGROUP_PakFile);
DECLARE_FLOAT_ACCUMULATOR_STAT(TEXT("PakCache Decrypt Time"), STAT_PakCache_DecryptTime, STATGROUP_PakFile);
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("PakCache Async Decrypts (Compressed Path)"), STAT_PakCache_CompressedDecrypts, STATGROUP_PakFile);
DECLARE_DWORD_ACCUMULATOR_STAT(TEXT("PakCache Async Decrypts (Uncompressed Path)"), STAT_PakCache_UncompressedDecrypts, STATGROUP_PakFile);
inline void DecryptData(uint8* InData, uint32 InDataSize)
{
SCOPE_SECONDS_ACCUMULATOR(STAT_PakCache_DecryptTime);
const ANSICHAR* Key = FPakPlatformFile::GetPakEncryptionKey();
checkf(Key, TEXT("AES decryption has been requested, but no valid encryption key was available"));
FAES::DecryptData(InData, InDataSize, Key);
}
#if USE_PAK_PRECACHE
#include "TaskGraphInterfaces.h"
#define PAK_CACHE_GRANULARITY (64*1024)
static_assert((PAK_CACHE_GRANULARITY % FPakInfo::MaxChunkDataSize) == 0, "PAK_CACHE_GRANULARITY must be set to a multiple of FPakInfo::MaxChunkDataSize");
#define PAK_CACHE_MAX_REQUESTS (8)
#define PAK_CACHE_MAX_PRIORITY_DIFFERENCE_MERGE (AIOP_Normal - AIOP_Precache)
#define PAK_EXTRA_CHECKS DO_CHECK
DECLARE_MEMORY_STAT(TEXT("PakCache Current"), STAT_PakCacheMem, STATGROUP_Memory);
DECLARE_MEMORY_STAT(TEXT("PakCache High Water"), STAT_PakCacheHighWater, STATGROUP_Memory);
DECLARE_FLOAT_ACCUMULATOR_STAT(TEXT("PakCache Signing Chunk Hash Time"), STAT_PakCache_SigningChunkHashTime, STATGROUP_PakFile);
DECLARE_MEMORY_STAT(TEXT("PakCache Signing Chunk Hash Size"), STAT_PakCache_SigningChunkHashSize, STATGROUP_PakFile);
static int32 GPakCache_Enable = 1;
static FAutoConsoleVariableRef CVar_Enable(
TEXT("pakcache.Enable"),
GPakCache_Enable,
TEXT("If > 0, then enable the pak cache.")
);
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.")
);
int32 GPakCache_MaxRequestSizeToLowerLevelKB = 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.")
);
int32 GPakCache_NumUnreferencedBlocksToCache = 10;
static FAutoConsoleVariableRef CVar_NumUnreferencedBlocksToCache(
TEXT("pakcache.NumUnreferencedBlocksToCache"),
GPakCache_NumUnreferencedBlocksToCache,
TEXT("Controls the maximum number of unreferenced blocks to keep. This is a classic disk cache and the maxmimum wasted memory is pakcache.MaxRequestSizeToLowerLevellKB * pakcache.NumUnreferencedBlocksToCache.")
);
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 < (uint32)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 < (uint32)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 < (uint32)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];
TArray<TPakChunkHash> ChunkHashes;
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+1);
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;
TArray<FJoinedOffsetAndPakIndex> OffsetAndPakIndexOfSavedBlocked;
struct FRequestToLower
{
IAsyncReadRequest* RequestHandle;
TIntervalTreeIndex BlockIndex;
int64 RequestSize;
uint8* Memory;
FRequestToLower()
: RequestHandle(nullptr)
, BlockIndex(IntervalTreeInvalidIndex)
, RequestSize(0)
, Memory(nullptr)
{
}
};
FRequestToLower RequestsToLower[PAK_CACHE_MAX_REQUESTS];
TArray<IAsyncReadRequest*> RequestsToDelete;
int32 NotifyRecursion;
uint32 Loads;
uint32 Frees;
uint64 LoadSize;
FEncryptionKey EncryptionKey;
bool bSigned;
public:
static void Init(IPlatformFile* InLowerLevel, const FEncryptionKey& InEncryptionKey)
{
if (!PakPrecacherSingleton)
{
verify(!FPlatformAtomics::InterlockedCompareExchangePointer((void**)&PakPrecacherSingleton, new FPakPrecacher(InLowerLevel, InEncryptionKey), nullptr));
}
check(PakPrecacherSingleton);
}
static void Shutdown()
{
if (PakPrecacherSingleton)
{
FPakPrecacher* LocalPakPrecacherSingleton = PakPrecacherSingleton;
if (LocalPakPrecacherSingleton && LocalPakPrecacherSingleton == FPlatformAtomics::InterlockedCompareExchangePointer((void**)&PakPrecacherSingleton, nullptr, LocalPakPrecacherSingleton))
{
LocalPakPrecacherSingleton->TrimCache(true);
double StartTime = FPlatformTime::Seconds();
while (!LocalPakPrecacherSingleton->IsProbablyIdle())
{
FPlatformProcess::SleepNoStats(0.001f);
if (FPlatformTime::Seconds() - StartTime > 10.0)
{
UE_LOG(LogPakFile, Error, TEXT("FPakPrecacher was not idle after 10s, exiting anyway and leaking."));
return;
}
}
delete PakPrecacherSingleton;
}
}
check(!PakPrecacherSingleton);
}
static FPakPrecacher& Get()
{
check(PakPrecacherSingleton);
return *PakPrecacherSingleton;
}
FPakPrecacher(IPlatformFile* InLowerLevel, const FEncryptionKey& InEncryptionKey)
: LowerLevel(InLowerLevel)
, LastReadRequest(0)
, NextUniqueID(1)
, BlockMemory(0)
, BlockMemoryHighWater(0)
, NotifyRecursion(0)
, Loads(0)
, Frees(0)
, LoadSize(0)
, EncryptionKey(InEncryptionKey)
, bSigned(!InEncryptionKey.Exponent.IsZero() && !InEncryptionKey.Modulus.IsZero())
{
check(LowerLevel && FPlatformProcess::SupportsMultithreading());
GPakCache_MaxRequestsToLowerLevel = FMath::Max(FMath::Min(FPlatformMisc::NumberOfIOWorkerThreadsToSpawn(), GPakCache_MaxRequestsToLowerLevel), 1);
check(GPakCache_MaxRequestsToLowerLevel <= PAK_CACHE_MAX_REQUESTS);
}
void StartSignatureCheck(bool bWasCanceled, IAsyncReadRequest* Request, int32 IndexToFill);
void DoSignatureCheck(bool bWasCanceled, IAsyncReadRequest* Request, int32 IndexToFill);
IPlatformFile* GetLowerLevelHandle()
{
check(LowerLevel);
return LowerLevel;
}
bool HasEnoughRoomForPrecache()
{
return GPakCache_AcceptPrecacheRequests;
}
uint16* RegisterPakFile(FName File, int64 PakFileSize)
{
uint16* PakIndexPtr = CachedPaks.Find(File);
if (!PakIndexPtr)
{
check(CachedPakData.Num() < MAX_uint16);
IAsyncReadFileHandle* Handle = LowerLevel->OpenAsyncRead(*File.ToString());
if (!Handle)
{
return nullptr;
}
CachedPakData.Add(FPakData(Handle, File, PakFileSize));
PakIndexPtr = &CachedPaks.Add(File, CachedPakData.Num() - 1);
UE_LOG(LogPakFile, Log, TEXT("New pak file %s added to pak precacher."), *File.ToString());
FPakData& Pak = CachedPakData[*PakIndexPtr];
if (bSigned)
{
// Load signature data
FString SignaturesFilename = FPaths::ChangeExtension(File.ToString(), TEXT("sig"));
IFileHandle* SignaturesFile = LowerLevel->OpenRead(*SignaturesFilename);
ensure(SignaturesFile);
FArchiveFileReaderGeneric* Reader = new FArchiveFileReaderGeneric(SignaturesFile, *SignaturesFilename, SignaturesFile->Size());
FEncryptedSignature MasterSignature;
*Reader << MasterSignature;
*Reader << Pak.ChunkHashes;
delete Reader;
// Check that we have the correct match between signature and pre-cache granularity
int64 NumPakChunks = Align(PakFileSize, FPakInfo::MaxChunkDataSize) / FPakInfo::MaxChunkDataSize;
ensure(NumPakChunks == Pak.ChunkHashes.Num());
// Decrypt signature hash
FDecryptedSignature DecryptedSignature;
FEncryption::DecryptSignature(MasterSignature, DecryptedSignature, EncryptionKey);
// Check the signatures are still as we expected them
TPakChunkHash Hash = ComputePakChunkHash(&Pak.ChunkHashes[0], Pak.ChunkHashes.Num() * sizeof(TPakChunkHash));
ensure(Hash == DecryptedSignature.Data);
}
}
return PakIndexPtr;
}
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 (uint32 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 (uint32 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 (uint32 Index = 0; Index < NumQWords; Index++)
{
if (InFlightOrDone[Index] != MAX_uint64)
{
Request.Status = EInRequestStatus::Waiting;
break;
}
}
}
}
else
{
#if PAK_EXTRA_CHECKS
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)
{
UE_LOG(LogPakFile, Verbose, TEXT("FPakReadRequest[%016llX, %016llX) ClearBlock"), Block.OffsetAndPakIndex, Block.OffsetAndPakIndex + Block.Size);
if (Block.Memory)
{
check(Block.Size);
BlockMemory -= Block.Size;
DEC_MEMORY_STAT_BY(STAT_PakCacheMem, Block.Size);
check(BlockMemory >= 0);
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 TrimCache(bool bDiscardAll = false)
{
// CachedFilesScopeLock is locked
int32 NumToKeep = bDiscardAll ? 0 : GPakCache_NumUnreferencedBlocksToCache;
int32 NumToRemove = FMath::Max<int32>(0, OffsetAndPakIndexOfSavedBlocked.Num() - NumToKeep);
if (NumToRemove)
{
for (int32 Index = 0; Index < NumToRemove; Index++)
{
FJoinedOffsetAndPakIndex OffsetAndPakIndex = OffsetAndPakIndexOfSavedBlocked[Index];
uint16 PakIndex = GetRequestPakIndex(OffsetAndPakIndex);
int64 Offset = GetRequestOffset(OffsetAndPakIndex);
FPakData& Pak = CachedPakData[PakIndex];
MaybeRemoveOverlappingNodesInIntervalTree<FCacheBlock>(
&Pak.CacheBlocks[(int32)EBlockStatus::Complete],
CacheBlockAllocator,
Offset,
Offset,
0,
Pak.MaxNode,
Pak.StartShift,
Pak.MaxShift,
[this](TIntervalTreeIndex BlockIndex) -> bool
{
FCacheBlock &Block = CacheBlockAllocator.Get(BlockIndex);
if (!Block.InRequestRefCount)
{
UE_LOG(LogPakFile, Verbose, TEXT("FPakReadRequest[%016llX, %016llX) Discard Cached"), Block.OffsetAndPakIndex, Block.OffsetAndPakIndex + Block.Size);
ClearBlock(Block);
return true;
}
return false;
}
);
}
OffsetAndPakIndexOfSavedBlocked.RemoveAt(0, NumToRemove, false);
}
}
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))
{
int64 OffsetOfLastByte = Offset + Size - 1;
MaybeRemoveOverlappingNodesInIntervalTree<FCacheBlock>(
&Pak.CacheBlocks[(int32)EBlockStatus::Complete],
CacheBlockAllocator,
Offset,
OffsetOfLastByte,
0,
Pak.MaxNode,
Pak.StartShift,
Pak.MaxShift,
[this, OffsetOfLastByte](TIntervalTreeIndex BlockIndex) -> bool
{
FCacheBlock &Block = CacheBlockAllocator.Get(BlockIndex);
check(Block.InRequestRefCount);
if (!--Block.InRequestRefCount)
{
if (GPakCache_NumUnreferencedBlocksToCache && GetRequestOffset(Block.OffsetAndPakIndex) + Block.Size > OffsetOfLastByte) // last block
{
OffsetAndPakIndexOfSavedBlocked.Remove(Block.OffsetAndPakIndex);
OffsetAndPakIndexOfSavedBlocked.Add(Block.OffsetAndPakIndex);
return false;
}
ClearBlock(Block);
return true;
}
return false;
}
);
TrimCache();
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;
bool bAnyOutstanding = false;
for (EAsyncIOPriority Priority = AIOP_MAX;; Priority = EAsyncIOPriority(int32(Priority) - 1))
{
if (Priority == AIOP_Precache && !bAcceptingPrecacheRequests && bAnyOutstanding)
{
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::Complete] != IntervalTreeInvalidIndex)
{
bAnyOutstanding = true;
}
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 + (GPakCache_MaxRequestSizeToLowerLevelKB * 1024), 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 (uint32 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 <= (GPakCache_MaxRequestSizeToLowerLevelKB * 1024));
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 PAK_EXTRA_CHECKS
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
#define CHECK_REDUNDANT_READS (0)
#if CHECK_REDUNDANT_READS
static struct FRedundantReadTracker
{
TMap<int64, double> LastReadTime;
int32 NumRedundant;
FRedundantReadTracker()
: NumRedundant(0)
{
}
void CheckBlock(int64 Offset, int64 Size)
{
double NowTime = FPlatformTime::Seconds();
int64 StartBlock = Offset / PAK_CACHE_GRANULARITY;
int64 LastBlock = (Offset + Size - 1) / PAK_CACHE_GRANULARITY;
for (int64 CurBlock = StartBlock; CurBlock <= LastBlock; CurBlock++)
{
double LastTime = LastReadTime.FindRef(CurBlock);
if (LastTime > 0.0 && NowTime - LastTime < 3.0)
{
NumRedundant++;
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Redundant read at block %d, %6.1fms ago (%d total redundant blocks)\r\n"), int32(CurBlock), 1000.0f * float(NowTime - LastTime), NumRedundant);
}
LastReadTime.Add(CurBlock, NowTime);
}
}
} RedundantReadTracker;
#else
static struct FRedundantReadTracker
{
FORCEINLINE void CheckBlock(int64 Offset, int64 Size)
{
}
} RedundantReadTracker;
#endif
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;
RequestsToLower[IndexToFill].RequestSize = Block.Size;
RequestsToLower[IndexToFill].Memory = nullptr;
check(&CacheBlockAllocator.Get(RequestsToLower[IndexToFill].BlockIndex) == &Block);
FAsyncFileCallBack CallbackFromLower =
[this, IndexToFill](bool bWasCanceled, IAsyncReadRequest* Request)
{
if (bSigned)
{
StartSignatureCheck(bWasCanceled, Request, IndexToFill);
}
else
{
NewRequestsToLowerComplete(bWasCanceled, Request, IndexToFill);
}
};
RequestsToLower[IndexToFill].RequestHandle = Pak.Handle->ReadRequest(GetRequestOffset(Block.OffsetAndPakIndex), Block.Size, Priority, &CallbackFromLower);
RedundantReadTracker.CheckBlock(GetRequestOffset(Block.OffsetAndPakIndex), Block.Size);
LastReadRequest = Block.OffsetAndPakIndex + Block.Size;
Loads++;
LoadSize += Block.Size;
}
void CompleteRequest(bool bWasCanceled, uint8* Memory, 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)
{
FMemory::Free(Memory);
UE_LOG(LogPakFile, Verbose, TEXT("FPakReadRequest[%016llX, %016llX) Cancelled"), Block.OffsetAndPakIndex, Block.OffsetAndPakIndex + Block.Size);
ClearBlock(Block);
}
else
{
Block.Memory = Memory;
check(Block.Memory && Block.Size);
BlockMemory += Block.Size;
check(BlockMemory > 0);
DEC_MEMORY_STAT_BY(STAT_AsyncFileMemory, Block.Size);
INC_MEMORY_STAT_BY(STAT_PakCacheMem, Block.Size);
if (BlockMemory > BlockMemoryHighWater)
{
BlockMemoryHighWater = BlockMemory;
SET_MEMORY_STAT(STAT_PakCacheHighWater, BlockMemoryHighWater);
#if 0
static int64 LastPrint = 0;
if (BlockMemoryHighWater / 1024 / 1024 /16 != LastPrint)
{
LastPrint = BlockMemoryHighWater / 1024 / 1024 / 16;
//FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Precache HighWater %dMB\r\n"), int32(LastPrint));
UE_LOG(LogPakFile, Log, TEXT("Precache HighWater %dMB\r\n"), int32(LastPrint * 16));
}
#endif
}
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;
}
bool GetCompletedRequestData(FPakInRequest& DoneRequest, uint8* Result)
{
// 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);
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 true;
}
///// Below here are the thread entrypoints
public:
void NewRequestsToLowerComplete(bool bWasCanceled, IAsyncReadRequest* Request, int32 Index)
{
FScopeLock Lock(&CachedFilesScopeLock);
RequestsToLower[Index].RequestHandle = Request;
ClearOldBlockTasks();
NotifyRecursion++;
if (!RequestsToLower[Index].Memory) // might have already been filled in by the signature check
{
RequestsToLower[Index].Memory = Request->GetReadResults();
}
CompleteRequest(bWasCanceled, RequestsToLower[Index].Memory, RequestsToLower[Index].BlockIndex);
RequestsToLower[Index].RequestHandle = nullptr;
RequestsToDelete.Add(Request);
RequestsToLower[Index].BlockIndex = IntervalTreeInvalidIndex;
StartNextRequest();
NotifyRecursion--;
}
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 = RegisterPakFile(File, PakFileSize);
if (PakIndexPtr == nullptr)
{
return false;
}
uint16 PakIndex = *PakIndexPtr;
FPakData& Pak = CachedPakData[PakIndex];
check(Pak.Name == File && Pak.TotalSize == PakFileSize && Pak.Handle);
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;
}
bool GetCompletedRequest(IPakRequestor* Owner, uint8* UserSuppliedMemory)
{
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);
return GetCompletedRequestData(Request, UserSuppliedMemory);
}
return false; // 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)
{
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);
}
StartNextRequest();
}
bool IsProbablyIdle() // nothing to prevent new requests from being made before I return
{
FScopeLock Lock(&CachedFilesScopeLock);
return !HasRequestsAtStatus(EInRequestStatus::Waiting) && !HasRequestsAtStatus(EInRequestStatus::InFlight);
}
void Unmount(FName PakFile)
{
FScopeLock Lock(&CachedFilesScopeLock);
uint16* PakIndexPtr = CachedPaks.Find(PakFile);
if (!PakIndexPtr)
{
UE_LOG(LogPakFile, Log, TEXT("Pak file %s was never used, so nothing to unmount"), *PakFile.ToString());
return; // never used for anything, nothing to check or clean up
}
TrimCache(true);
uint16 PakIndex = *PakIndexPtr;
FPakData& Pak = CachedPakData[PakIndex];
int64 Offset = MakeJoinedRequest(PakIndex, 0);
bool bHasOutstandingRequests = false;
OverlappingNodesInIntervalTree<FCacheBlock>(
Pak.CacheBlocks[(int32)EBlockStatus::Complete],
CacheBlockAllocator,
0,
Offset + Pak.TotalSize - 1,
0,
Pak.MaxNode,
Pak.StartShift,
Pak.MaxShift,
[&bHasOutstandingRequests](TIntervalTreeIndex BlockIndex) -> bool
{
check(!"Pak cannot be unmounted with outstanding requests");
bHasOutstandingRequests = true;
return false;
}
);
OverlappingNodesInIntervalTree<FCacheBlock>(
Pak.CacheBlocks[(int32)EBlockStatus::InFlight],
CacheBlockAllocator,
0,
Offset + Pak.TotalSize - 1,
0,
Pak.MaxNode,
Pak.StartShift,
Pak.MaxShift,
[&bHasOutstandingRequests](TIntervalTreeIndex BlockIndex) -> bool
{
check(!"Pak cannot be unmounted with outstanding requests");
bHasOutstandingRequests = true;
return false;
}
);
for (EAsyncIOPriority Priority = AIOP_MAX;; Priority = EAsyncIOPriority(int32(Priority) - 1))
{
OverlappingNodesInIntervalTree<FPakInRequest>(
Pak.InRequests[Priority][(int32)EInRequestStatus::InFlight],
InRequestAllocator,
0,
Offset + Pak.TotalSize - 1,
0,
Pak.MaxNode,
Pak.StartShift,
Pak.MaxShift,
[&bHasOutstandingRequests](TIntervalTreeIndex BlockIndex) -> bool
{
check(!"Pak cannot be unmounted with outstanding requests");
bHasOutstandingRequests = true;
return false;
}
);
OverlappingNodesInIntervalTree<FPakInRequest>(
Pak.InRequests[Priority][(int32)EInRequestStatus::Complete],
InRequestAllocator,
0,
Offset + Pak.TotalSize - 1,
0,
Pak.MaxNode,
Pak.StartShift,
Pak.MaxShift,
[&bHasOutstandingRequests](TIntervalTreeIndex BlockIndex) -> bool
{
check(!"Pak cannot be unmounted with outstanding requests");
bHasOutstandingRequests = true;
return false;
}
);
OverlappingNodesInIntervalTree<FPakInRequest>(
Pak.InRequests[Priority][(int32)EInRequestStatus::Waiting],
InRequestAllocator,
0,
Offset + Pak.TotalSize - 1,
0,
Pak.MaxNode,
Pak.StartShift,
Pak.MaxShift,
[&bHasOutstandingRequests](TIntervalTreeIndex BlockIndex) -> bool
{
check(!"Pak cannot be unmounted with outstanding requests");
bHasOutstandingRequests = true;
return false;
}
);
if (Priority == AIOP_MIN)
{
break;
}
}
if (!bHasOutstandingRequests)
{
UE_LOG(LogPakFile, Log, TEXT("Pak file %s removed from pak precacher."), *PakFile.ToString());
CachedPaks.Remove(PakFile);
check(Pak.Handle);
delete Pak.Handle;
Pak.Handle = nullptr;
int32 NumToTrim = 0;
for (int32 Index = CachedPakData.Num() - 1; Index >= 0; Index--)
{
if (!CachedPakData[Index].Handle)
{
NumToTrim++;
}
else
{
break;
}
}
if (NumToTrim)
{
CachedPakData.RemoveAt(CachedPakData.Num() - NumToTrim, NumToTrim);
}
}
else
{
UE_LOG(LogPakFile, Log, TEXT("Pak file %s was NOT removed from pak precacher because it had outstanding requests."), *PakFile.ToString());
}
}
// 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 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 FPakAsyncReadFileHandle;
//uncompress(unencrypt(checksig())))
class FPakReadRequestBase : public IAsyncReadRequest, public IPakRequestor
{
protected:
int64 Offset;
int64 BytesToRead;
FEvent* WaitEvent;
int32 BlockIndex;
EAsyncIOPriority Priority;
bool bRequestOutstanding;
bool bNeedsRemoval;
bool bInternalRequest; // we are using this internally to deal with compressed, encrypted and signed, so we want the memory back from a precache request.
public:
FPakReadRequestBase(FName InPakFile, int64 PakFileSize, FAsyncFileCallBack* CompleteCallback, int64 InOffset, int64 InBytesToRead, EAsyncIOPriority InPriority, uint8* UserSuppliedMemory, bool bInInternalRequest = false, int32 InBlockIndex = -1)
: IAsyncReadRequest(CompleteCallback, false, UserSuppliedMemory)
, Offset(InOffset)
, BytesToRead(InBytesToRead)
, WaitEvent(nullptr)
, BlockIndex(InBlockIndex)
, Priority(InPriority)
, bRequestOutstanding(true)
, bNeedsRemoval(true)
, bInternalRequest(bInInternalRequest)
{
}
virtual ~FPakReadRequestBase()
{
if (bNeedsRemoval)
{
FPakPrecacher::Get().CancelRequest(this);
}
if (Memory && !bUserSuppliedMemory)
{
// this can happen with a race on cancel, it is ok, they didn't take the memory, free it now
DEC_MEMORY_STAT_BY(STAT_AsyncFileMemory, BytesToRead);
FMemory::Free(Memory);
}
Memory = nullptr;
}
// 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
int32 GetBlockIndex()
{
return BlockIndex;
}
};
class FPakReadRequest : public FPakReadRequestBase
{
public:
FPakReadRequest(FName InPakFile, int64 PakFileSize, FAsyncFileCallBack* CompleteCallback, int64 InOffset, int64 InBytesToRead, EAsyncIOPriority InPriority, uint8* UserSuppliedMemory, bool bInInternalRequest = false, int32 InBlockIndex = -1)
: FPakReadRequestBase(InPakFile, PakFileSize, CompleteCallback, InOffset, InBytesToRead, InPriority, UserSuppliedMemory, bInInternalRequest, InBlockIndex)
{
check(Offset >= 0 && BytesToRead > 0);
check(bInternalRequest || Priority > AIOP_Precache || !bUserSuppliedMemory); // you never get bits back from a precache request, so why supply memory?
if (!FPakPrecacher::Get().QueueRequest(this, InPakFile, PakFileSize, Offset, BytesToRead, Priority))
{
bRequestOutstanding = false;
SetComplete();
}
}
virtual void RequestIsComplete() override
{
check(bRequestOutstanding);
if (!bCanceled && (bInternalRequest || Priority > AIOP_Precache))
{
if (!bUserSuppliedMemory)
{
check(!Memory);
Memory = (uint8*)FMemory::Malloc(BytesToRead);
INC_MEMORY_STAT_BY(STAT_AsyncFileMemory, BytesToRead);
}
else
{
check(Memory);
}
if (!FPakPrecacher::Get().GetCompletedRequest(this, Memory))
{
check(bCanceled);
}
}
SetComplete();
FScopeLock Lock(&FPakReadRequestEvent);
bRequestOutstanding = false;
if (WaitEvent)
{
WaitEvent->Trigger();
}
}
};
class FPakEncryptedReadRequest : public FPakReadRequestBase
{
int64 OriginalOffset;
int64 OriginalSize;
public:
FPakEncryptedReadRequest(FName InPakFile, int64 PakFileSize, FAsyncFileCallBack* CompleteCallback, int64 InPakFileStartOffset, int64 InFileOffset, int64 InBytesToRead, EAsyncIOPriority InPriority, uint8* UserSuppliedMemory, bool bInInternalRequest = false, int32 InBlockIndex = -1)
: FPakReadRequestBase(InPakFile, PakFileSize, CompleteCallback, InPakFileStartOffset + InFileOffset, InBytesToRead, InPriority, UserSuppliedMemory, bInInternalRequest, InBlockIndex)
, OriginalOffset(InPakFileStartOffset + InFileOffset)
, OriginalSize(InBytesToRead)
{
Offset = InPakFileStartOffset + AlignDown(InFileOffset, FAES::AESBlockSize);
BytesToRead = Align(InBytesToRead, FAES::AESBlockSize);
if (!FPakPrecacher::Get().QueueRequest(this, InPakFile, PakFileSize, Offset, BytesToRead, Priority))
{
bRequestOutstanding = false;
SetComplete();
}
}
virtual void RequestIsComplete() override
{
check(bRequestOutstanding);
if (!bCanceled && (bInternalRequest || Priority > AIOP_Precache))
{
uint8* OversizedBuffer = nullptr;
if (OriginalOffset != Offset)
{
// We've read some bytes from before the requested offset, so we need to grab that larger amount
// from read request and then cut out the bit we want!
OversizedBuffer = (uint8*)FMemory::Malloc(BytesToRead);
}
if (!bUserSuppliedMemory)
{
check(!Memory);
Memory = (uint8*)FMemory::Malloc(OriginalSize);
INC_MEMORY_STAT_BY(STAT_AsyncFileMemory, OriginalSize);
}
else
{
check(Memory);
}
if (!FPakPrecacher::Get().GetCompletedRequest(this, OversizedBuffer != nullptr ? OversizedBuffer : Memory))
{
check(bCanceled);
}
INC_DWORD_STAT(STAT_PakCache_UncompressedDecrypts);
if (OversizedBuffer)
{
check(IsAligned((void*)BytesToRead, FAES::AESBlockSize));
DecryptData(OversizedBuffer, BytesToRead);
FMemory::Memcpy(Memory, OversizedBuffer + (OriginalOffset - Offset), OriginalSize);
FMemory::Free(OversizedBuffer);
}
else
{
DecryptData(Memory, Align(OriginalSize, FAES::AESBlockSize));
}
}
SetComplete();
FScopeLock Lock(&FPakReadRequestEvent);
bRequestOutstanding = false;
if (WaitEvent)
{
WaitEvent->Trigger();
}
}
};
class FPakSizeRequest : public IAsyncReadRequest
{
public:
FPakSizeRequest(FAsyncFileCallBack* CompleteCallback, int64 InFileSize)
: IAsyncReadRequest(CompleteCallback, true, nullptr)
{
Size = InFileSize;
SetComplete();
}
virtual void WaitCompletionImpl(float TimeLimitSeconds) override
{
}
virtual void CancelImpl()
{
}
};
struct FCachedAsyncBlock
{
FPakReadRequest* RawRequest;
uint8* Raw; // compressed, encrypted and/or signature not checked
uint8* Processed; // decompressed, deencrypted and signature checked
FGraphEventRef CPUWorkGraphEvent;
int32 RawSize;
int32 ProcessedSize;
int32 RefCount;
bool bInFlight;
bool bCPUWorkIsComplete;
FCachedAsyncBlock()
: RawRequest(0)
, Raw(nullptr)
, Processed(nullptr)
, RawSize(0)
, ProcessedSize(0)
, RefCount(0)
, bInFlight(false)
, bCPUWorkIsComplete(false)
{
}
};
class FPakProcessedReadRequest : public IAsyncReadRequest
{
FPakAsyncReadFileHandle* Owner;
int64 Offset;
int64 BytesToRead;
FEvent* WaitEvent;
FThreadSafeCounter CompleteRace; // this is used to resolve races with natural completion and cancel; there can be only one.
EAsyncIOPriority Priority;
bool bRequestOutstanding;
bool bHasCancelled;
bool bHasCompleted;
public:
FPakProcessedReadRequest(FPakAsyncReadFileHandle* InOwner, FAsyncFileCallBack* CompleteCallback, int64 InOffset, int64 InBytesToRead, EAsyncIOPriority InPriority, uint8* UserSuppliedMemory)
: IAsyncReadRequest(CompleteCallback, false, UserSuppliedMemory)
, Owner(InOwner)
, Offset(InOffset)
, BytesToRead(InBytesToRead)
, WaitEvent(nullptr)
, Priority(InPriority)
, bRequestOutstanding(true)
, bHasCancelled(false)
, bHasCompleted(false)
{
check(Offset >= 0 && BytesToRead > 0);
check(Priority > AIOP_Precache || !bUserSuppliedMemory); // you never get bits back from a precache request, so why supply memory?
}
virtual ~FPakProcessedReadRequest()
{
DoneWithRawRequests();
if (Memory && !bUserSuppliedMemory)
{
// this can happen with a race on cancel, it is ok, they didn't take the memory, free it now
DEC_MEMORY_STAT_BY(STAT_AsyncFileMemory, BytesToRead);
FMemory::Free(Memory);
}
Memory = nullptr;
}
// 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
if (bRequestOutstanding)
{
CancelRawRequests();
bRequestOutstanding = false;
SetComplete();
}
check(!WaitEvent); // you canceled from a different thread that you waited from
}
void RequestIsComplete()
{
check(bRequestOutstanding);
if (!bCanceled && Priority > AIOP_Precache)
{
GatherResults();
}
SetComplete();
FScopeLock Lock(&FPakReadRequestEvent);
bRequestOutstanding = false;
if (WaitEvent)
{
WaitEvent->Trigger();
}
}
void GatherResults();
void DoneWithRawRequests();
bool CheckCompletion(const FPakEntry& FileEntry, int32 BlockIndex, TArray<FCachedAsyncBlock>& Blocks);
void CancelRawRequests();
};
FAutoConsoleTaskPriority CPrio_AsyncIOCPUWorkTaskPriority(
TEXT("TaskGraph.TaskPriorities.AsyncIOCPUWork"),
TEXT("Task and thread priority for decompression, decryption and signature checking of async IO from a pak file."),
ENamedThreads::BackgroundThreadPriority, // if we have background priority task threads, then use them...
ENamedThreads::NormalTaskPriority, // .. at normal task priority
ENamedThreads::NormalTaskPriority // if we don't have background threads, then use normal priority threads at normal task priority instead
);
class FAsyncIOCPUWorkTask
{
FPakAsyncReadFileHandle& Owner;
int32 BlockIndex;
public:
FORCEINLINE FAsyncIOCPUWorkTask(FPakAsyncReadFileHandle& InOwner, int32 InBlockIndex)
: Owner(InOwner)
, BlockIndex(InBlockIndex)
{
}
static FORCEINLINE TStatId GetStatId()
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FsyncIOCPUWorkTask, STATGROUP_TaskGraphTasks);
}
static FORCEINLINE ENamedThreads::Type GetDesiredThread()
{
return CPrio_AsyncIOCPUWorkTaskPriority.Get();
}
FORCEINLINE static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::TrackSubsequents;
}
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent);
};
class FAsyncIOSignatureCheckTask
{
bool bWasCanceled;
IAsyncReadRequest* Request;
int32 IndexToFill;
public:
FORCEINLINE FAsyncIOSignatureCheckTask(bool bInWasCanceled, IAsyncReadRequest* InRequest, int32 InIndexToFill)
: bWasCanceled(bInWasCanceled)
, Request(InRequest)
, IndexToFill(InIndexToFill)
{
}
static FORCEINLINE TStatId GetStatId()
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FsyncIOCPUWorkTask, STATGROUP_TaskGraphTasks);
}
static FORCEINLINE ENamedThreads::Type GetDesiredThread()
{
return CPrio_AsyncIOCPUWorkTaskPriority.Get();
}
FORCEINLINE static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::TrackSubsequents;
}
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
FPakPrecacher::Get().DoSignatureCheck(bWasCanceled, Request, IndexToFill);
}
};
void FPakPrecacher::StartSignatureCheck(bool bWasCanceled, IAsyncReadRequest* Request, int32 Index)
{
TGraphTask<FAsyncIOSignatureCheckTask>::CreateTask().ConstructAndDispatchWhenReady(bWasCanceled, Request, Index);
}
void FPakPrecacher::DoSignatureCheck(bool bWasCanceled, IAsyncReadRequest* Request, int32 Index)
{
int64 SignatureIndex = -1;
int64 NumSignaturesToCheck = 0;
const uint8* Data = nullptr;
int64 RequestSize = 0;
int64 RequestOffset = 0;
uint16 PakIndex;
{
// Try and keep lock for as short a time as possible. Find our request and copy out the data we need
FScopeLock Lock(&CachedFilesScopeLock);
FRequestToLower& RequestToLower = RequestsToLower[Index];
RequestToLower.RequestHandle = Request;
RequestToLower.Memory = Request->GetReadResults();
NumSignaturesToCheck = Align(RequestToLower.RequestSize, FPakInfo::MaxChunkDataSize) / FPakInfo::MaxChunkDataSize;
check(NumSignaturesToCheck >= 1);
FCacheBlock& Block = CacheBlockAllocator.Get(RequestToLower.BlockIndex);
RequestOffset = GetRequestOffset(Block.OffsetAndPakIndex);
check((RequestOffset % FPakInfo::MaxChunkDataSize) == 0);
RequestSize = RequestToLower.RequestSize;
PakIndex = GetRequestPakIndex(Block.OffsetAndPakIndex);
Data = RequestToLower.Memory;
SignatureIndex = RequestOffset / FPakInfo::MaxChunkDataSize;
}
check(Data);
check(NumSignaturesToCheck > 0);
check(RequestSize > 0);
check(RequestOffset >= 0);
// Hash the contents of the incoming buffer and check that it matches what we expected
for (int64 SignedChunkIndex = 0; SignedChunkIndex < NumSignaturesToCheck; ++SignedChunkIndex, ++SignatureIndex)
{
int64 Size = FMath::Min(RequestSize, (int64)FPakInfo::MaxChunkDataSize);
{
SCOPE_SECONDS_ACCUMULATOR(STAT_PakCache_SigningChunkHashTime);
TPakChunkHash ThisHash = ComputePakChunkHash(Data, Size);
bool bChunkHashesMatch;
{
FScopeLock Lock(&CachedFilesScopeLock);
FPakData* PakData = &CachedPakData[PakIndex];
bChunkHashesMatch = ThisHash == PakData->ChunkHashes[SignatureIndex];
}
ensure(bChunkHashesMatch);
if (!ensure(bChunkHashesMatch))
{
UE_LOG(LogPakFile, Warning, TEXT("Pak chunk signing mismatch! Pak file has been corrupted or tampered with!"));
//FPlatformMisc::RequestExit(true);
}
}
INC_MEMORY_STAT_BY(STAT_PakCache_SigningChunkHashSize, Size);
RequestOffset += Size;
Data += Size;
RequestSize -= Size;
}
NewRequestsToLowerComplete(bWasCanceled, Request, Index);
}
class FPakAsyncReadFileHandle final : public IAsyncReadFileHandle
{
FName PakFile;
int64 PakFileSize;
int64 OffsetInPak;
int64 CompressedFileSize;
int64 UncompressedFileSize;
const FPakEntry* FileEntry;
TSet<FPakProcessedReadRequest*> LiveRequests;
TArray<FCachedAsyncBlock> Blocks;
FAsyncFileCallBack ReadCallbackFunction;
FCriticalSection CriticalSection;
int32 NumLiveRawRequests;
public:
FPakAsyncReadFileHandle(const FPakEntry* InFileEntry, FPakFile* InPakFile, const TCHAR* Filename)
: PakFile(InPakFile->GetFilenameName())
, PakFileSize(InPakFile->TotalSize())
, FileEntry(InFileEntry)
, NumLiveRawRequests(0)
{
OffsetInPak = FileEntry->Offset + FileEntry->GetSerializedSize(InPakFile->GetInfo().Version);
UncompressedFileSize = FileEntry->UncompressedSize;
CompressedFileSize = FileEntry->UncompressedSize;
if (FileEntry->CompressionMethod != COMPRESS_None && UncompressedFileSize)
{
check(FileEntry->CompressionBlocks.Num());
CompressedFileSize = FileEntry->CompressionBlocks.Last().CompressedEnd - OffsetInPak;
check(CompressedFileSize > 0);
const int32 CompressionBlockSize = FileEntry->CompressionBlockSize;
check((UncompressedFileSize + CompressionBlockSize - 1) / CompressionBlockSize == FileEntry->CompressionBlocks.Num());
Blocks.AddDefaulted(FileEntry->CompressionBlocks.Num());
}
UE_LOG(LogPakFile, Verbose, TEXT("FPakPlatformFile::OpenAsyncRead[%016llX, %016llX) %s"), OffsetInPak, OffsetInPak + CompressedFileSize, Filename);
check(PakFileSize > 0 && OffsetInPak + CompressedFileSize <= PakFileSize && OffsetInPak >= 0);
ReadCallbackFunction = [this](bool bWasCancelled, IAsyncReadRequest* Request)
{
RawReadCallback(bWasCancelled, Request);
};
}
~FPakAsyncReadFileHandle()
{
check(!LiveRequests.Num()); // must delete all requests before you delete the handle
check(!NumLiveRawRequests); // must delete all requests before you delete the handle
for (FCachedAsyncBlock& Block : Blocks)
{
check(Block.RefCount == 0);
ClearBlock(Block, true);
}
}
virtual IAsyncReadRequest* SizeRequest(FAsyncFileCallBack* CompleteCallback = nullptr) override
{
return new FPakSizeRequest(CompleteCallback, UncompressedFileSize);
}
virtual IAsyncReadRequest* ReadRequest(int64 Offset, int64 BytesToRead, EAsyncIOPriority Priority = AIOP_Normal, FAsyncFileCallBack* CompleteCallback = nullptr, uint8* UserSuppliedMemory = nullptr) override
{
if (BytesToRead == MAX_int64)
{
BytesToRead = UncompressedFileSize - Offset;
}
check(Offset + BytesToRead <= UncompressedFileSize && Offset >= 0);
if (FileEntry->CompressionMethod == COMPRESS_None)
{
check(Offset + BytesToRead + OffsetInPak <= PakFileSize);
check(!Blocks.Num());
if (FileEntry->bEncrypted)
{
return new FPakEncryptedReadRequest(PakFile, PakFileSize, CompleteCallback, OffsetInPak, Offset, BytesToRead, Priority, UserSuppliedMemory);
}
else
{
return new FPakReadRequest(PakFile, PakFileSize, CompleteCallback, OffsetInPak + Offset, BytesToRead, Priority, UserSuppliedMemory);
}
}
bool bAnyUnfinished = false;
FPakProcessedReadRequest* Result;
{
FScopeLock ScopedLock(&CriticalSection);
check(Blocks.Num());
int32 FirstBlock = Offset / FileEntry->CompressionBlockSize;
int32 LastBlock = (Offset + BytesToRead - 1) / FileEntry->CompressionBlockSize;
check(FirstBlock >= 0 && FirstBlock < Blocks.Num() && LastBlock >= 0 && LastBlock < Blocks.Num() && FirstBlock <= LastBlock);
Result = new FPakProcessedReadRequest(this, CompleteCallback, Offset, BytesToRead, Priority, UserSuppliedMemory);
for (int32 BlockIndex = FirstBlock; BlockIndex <= LastBlock; BlockIndex++)
{
FCachedAsyncBlock& Block = Blocks[BlockIndex];
Block.RefCount++;
if (!Block.bInFlight)
{
StartBlock(BlockIndex, Priority);
bAnyUnfinished = true;
}
if (!Block.Processed)
{
bAnyUnfinished = true;
}
}
if (Result)
{
check(!LiveRequests.Contains(Result))
LiveRequests.Add(Result);
}
if (!bAnyUnfinished)
{
Result->RequestIsComplete();
}
}
return Result;
}
void StartBlock(int32 BlockIndex, EAsyncIOPriority Priority)
{
FCachedAsyncBlock& Block = Blocks[BlockIndex];
Block.bInFlight = true;
check(!Block.RawRequest && !Block.Processed && !Block.Raw && !Block.CPUWorkGraphEvent.GetReference() && !Block.ProcessedSize && !Block.RawSize && !Block.bCPUWorkIsComplete);
Block.RawSize = FileEntry->CompressionBlocks[BlockIndex].CompressedEnd - FileEntry->CompressionBlocks[BlockIndex].CompressedStart;
if (FileEntry->bEncrypted)
{
Block.RawSize = Align(Block.RawSize, FAES::AESBlockSize);
}
NumLiveRawRequests++;
Block.RawRequest = new FPakReadRequest(PakFile, PakFileSize, &ReadCallbackFunction, FileEntry->CompressionBlocks[BlockIndex].CompressedStart, Block.RawSize, Priority, nullptr, true, BlockIndex);
}
void RawReadCallback(bool bWasCancelled, IAsyncReadRequest* InRequest)
{
FPakReadRequest* Request = static_cast<FPakReadRequest*>(InRequest);
// Causes a deadlock, hopefully not needed as we are only referencing the block.
// Potential problem is with cancel
// FScopeLock ScopedLock(&CriticalSection);
int32 BlockIndex = Request->GetBlockIndex();
check(BlockIndex >= 0 && BlockIndex < Blocks.Num());
FCachedAsyncBlock& Block = Blocks[BlockIndex];
check((Block.RawRequest == Request || (!Block.RawRequest && Block.RawSize)) // we still might be in the constructor so the assignment hasn't happened yet
&& !Block.Processed && !Block.Raw);
if (bWasCancelled)
{
Block.RawSize = 0;
}
else
{
Block.Raw = Request->GetReadResults();
check(Block.Raw);
Block.ProcessedSize = FileEntry->CompressionBlockSize;
if (BlockIndex == Blocks.Num() - 1)
{
Block.ProcessedSize = FileEntry->UncompressedSize % FileEntry->CompressionBlockSize;
if (!Block.ProcessedSize)
{
Block.ProcessedSize = FileEntry->CompressionBlockSize; // last block was a full block
}
}
check(Block.ProcessedSize && !Block.bCPUWorkIsComplete);
Block.CPUWorkGraphEvent = TGraphTask<FAsyncIOCPUWorkTask>::CreateTask().ConstructAndDispatchWhenReady(*this, BlockIndex);
}
}
void DoProcessing(int32 BlockIndex)
{
check(BlockIndex >= 0 && BlockIndex < Blocks.Num());
FCachedAsyncBlock& Block = Blocks[BlockIndex];
check(Block.Raw && Block.RawSize && !Block.Processed);
if (FileEntry->bEncrypted)
{
INC_DWORD_STAT(STAT_PakCache_CompressedDecrypts);
DecryptData(Block.Raw, Align(Block.RawSize, FAES::AESBlockSize));
}
check(Block.ProcessedSize);
INC_MEMORY_STAT_BY(STAT_AsyncFileMemory, Block.ProcessedSize);
uint8* Output = (uint8*)FMemory::Malloc(Block.ProcessedSize);
FCompression::UncompressMemory((ECompressionFlags)FileEntry->CompressionMethod, Output, Block.ProcessedSize, Block.Raw, Block.RawSize, false, FPlatformMisc::GetPlatformCompression()->GetCompressionBitWindow());
FMemory::Free(Block.Raw);
Block.Raw = nullptr;
DEC_MEMORY_STAT_BY(STAT_AsyncFileMemory, Block.RawSize);
Block.RawSize = 0;
{
FScopeLock ScopedLock(&CriticalSection);
if (Block.RawRequest)
{
Block.RawRequest->WaitCompletion();
delete Block.RawRequest;
Block.RawRequest = nullptr;
NumLiveRawRequests--;
}
if (Block.RefCount > 0)
{
Block.Processed = Output;
for (FPakProcessedReadRequest* Req : LiveRequests)
{
if (Req->CheckCompletion(*FileEntry, BlockIndex, Blocks))
{
Req->RequestIsComplete();
}
}
}
else
{
// must have been canceled, clean up
FMemory::Free(Output);
Output = nullptr;
check(Block.ProcessedSize);
DEC_MEMORY_STAT_BY(STAT_AsyncFileMemory, Block.ProcessedSize);
Block.ProcessedSize = 0;
Block.CPUWorkGraphEvent = nullptr;
Block.bInFlight = false;
}
Block.bCPUWorkIsComplete = true;
}
}
void ClearBlock(FCachedAsyncBlock& Block, bool bForDestructorShouldAlreadyBeClear = false)
{
check(!Block.RawRequest);
Block.RawRequest = nullptr;
Block.CPUWorkGraphEvent = nullptr;
if (Block.Raw)
{
check(!bForDestructorShouldAlreadyBeClear);
// this was a cancel, clean it up now
FMemory::Free(Block.Raw);
Block.Raw = nullptr;
check(Block.RawSize);
DEC_MEMORY_STAT_BY(STAT_AsyncFileMemory, Block.RawSize);
}
Block.RawSize = 0;
if (Block.Processed)
{
check(bForDestructorShouldAlreadyBeClear == false);
FMemory::Free(Block.Processed);
Block.Processed = nullptr;
check(Block.ProcessedSize);
DEC_MEMORY_STAT_BY(STAT_AsyncFileMemory, Block.ProcessedSize);
}
Block.ProcessedSize = 0;
Block.bInFlight = false;
Block.bCPUWorkIsComplete = false;
}
void RemoveRequest(FPakProcessedReadRequest* Req, int64 Offset, int64 BytesToRead)
{
FScopeLock ScopedLock(&CriticalSection);
check(LiveRequests.Contains(Req));
LiveRequests.Remove(Req);
int32 FirstBlock = Offset / FileEntry->CompressionBlockSize;
int32 LastBlock = (Offset + BytesToRead - 1) / FileEntry->CompressionBlockSize;
check(FirstBlock >= 0 && FirstBlock < Blocks.Num() && LastBlock >= 0 && LastBlock < Blocks.Num() && FirstBlock <= LastBlock);
for (int32 BlockIndex = FirstBlock; BlockIndex <= LastBlock; BlockIndex++)
{
FCachedAsyncBlock& Block = Blocks[BlockIndex];
check(Block.RefCount > 0);
if (!--Block.RefCount)
{
if (Block.RawRequest)
{
Block.RawRequest->Cancel();
Block.RawRequest->WaitCompletion();
delete Block.RawRequest;
Block.RawRequest = nullptr;
NumLiveRawRequests--;
}
if (Block.bCPUWorkIsComplete)
{
ClearBlock(Block);
}
}
}
}
void GatherResults(uint8* Memory, int64 Offset, int64 BytesToRead)
{
// no lock here, I don't think it is needed because we have a ref count.
int32 FirstBlock = Offset / FileEntry->CompressionBlockSize;
int32 LastBlock = (Offset + BytesToRead - 1) / FileEntry->CompressionBlockSize;
check(FirstBlock >= 0 && FirstBlock < Blocks.Num() && LastBlock >= 0 && LastBlock < Blocks.Num() && FirstBlock <= LastBlock);
for (int32 BlockIndex = FirstBlock; BlockIndex <= LastBlock; BlockIndex++)
{
FCachedAsyncBlock& Block = Blocks[BlockIndex];
check(Block.RefCount > 0 && Block.Processed && Block.ProcessedSize);
int64 BlockStart = int64(BlockIndex) * int64(FileEntry->CompressionBlockSize);
int64 BlockEnd = BlockStart + Block.ProcessedSize;
int64 SrcOffset = 0;
int64 DestOffset = BlockStart - Offset;
if (DestOffset < 0)
{
SrcOffset -= DestOffset;
DestOffset = 0;
}
int64 CopySize = Block.ProcessedSize;
if (DestOffset + CopySize > BytesToRead)
{
CopySize = BytesToRead - DestOffset;
}
if (SrcOffset + CopySize > Block.ProcessedSize)
{
CopySize = Block.ProcessedSize - SrcOffset;
}
check(CopySize > 0 && DestOffset >= 0 && DestOffset + CopySize <= BytesToRead);
check(SrcOffset >= 0 && SrcOffset + CopySize <= Block.ProcessedSize);
FMemory::Memcpy(Memory + DestOffset, Block.Processed + SrcOffset, CopySize);
check(Block.RefCount > 0);
}
}
};
void FPakProcessedReadRequest::CancelRawRequests()
{
if (CompleteRace.Increment() == 1)
{
Owner->RemoveRequest(this, Offset, BytesToRead);
bHasCancelled = true;
}
}
void FPakProcessedReadRequest::GatherResults()
{
if (CompleteRace.Increment() == 1)
{
if (!bUserSuppliedMemory)
{
check(!Memory);
Memory = (uint8*)FMemory::Malloc(BytesToRead);
INC_MEMORY_STAT_BY(STAT_AsyncFileMemory, BytesToRead);
}
check(Memory);
Owner->GatherResults(Memory, Offset, BytesToRead);
}
}
void FPakProcessedReadRequest::DoneWithRawRequests()
{
if (!bHasCancelled)
{
Owner->RemoveRequest(this, Offset, BytesToRead);
}
}
bool FPakProcessedReadRequest::CheckCompletion(const FPakEntry& FileEntry, int32 BlockIndex, TArray<FCachedAsyncBlock>& Blocks)
{
if (!bRequestOutstanding || bHasCompleted)
{
return false;
}
{
int64 BlockStart = int64(BlockIndex) * int64(FileEntry.CompressionBlockSize);
int64 BlockEnd = int64(BlockIndex + 1) * int64(FileEntry.CompressionBlockSize);
if (Offset >= BlockEnd || Offset + BytesToRead <= BlockStart)
{
return false;
}
}
int32 FirstBlock = Offset / FileEntry.CompressionBlockSize;
int32 LastBlock = (Offset + BytesToRead - 1) / FileEntry.CompressionBlockSize;
check(FirstBlock >= 0 && FirstBlock < Blocks.Num() && LastBlock >= 0 && LastBlock < Blocks.Num() && FirstBlock <= LastBlock);
for (int32 MyBlockIndex = FirstBlock; MyBlockIndex <= LastBlock; MyBlockIndex++)
{
FCachedAsyncBlock& Block = Blocks[MyBlockIndex];
if (!Block.Processed)
{
return false;
}
}
bHasCompleted = true;
return true;
}
void FAsyncIOCPUWorkTask::DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
Owner.DoProcessing(BlockIndex);
}
#endif
IAsyncReadFileHandle* FPakPlatformFile::OpenAsyncRead(const TCHAR* Filename)
{
check(GConfig);
#if USE_PAK_PRECACHE
if (FPlatformProcess::SupportsMultithreading() && GPakCache_Enable > 0)
{
FPakFile* PakFile = NULL;
const FPakEntry* FileEntry = FindFileInPakFiles(Filename, &PakFile);
if (FileEntry && PakFile && PakFile->GetFilenameName() != NAME_None)
{
return new FPakAsyncReadFileHandle(FileEntry, PakFile, Filename);
}
}
#endif
return IPlatformFile::OpenAsyncRead(Filename);
}
/**
* 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)
{
INC_DWORD_STAT(STAT_PakCache_SyncDecrypts);
DecryptData((uint8*)Data, Size);
}
};
/**
* Thread local class to manage working buffers for file compression
*/
class FCompressionScratchBuffers : public TThreadSingleton<FCompressionScratchBuffers>
{
public:
FCompressionScratchBuffers()
: TempBufferSize(0)
, ScratchBufferSize(0)
{}
int64 TempBufferSize;
TUniquePtr<uint8[]> TempBuffer;
int64 ScratchBufferSize;
TUniquePtr<uint8[]> ScratchBuffer;
void EnsureBufferSpace(int64 CompressionBlockSize, int64 ScrachSize)
{
if(TempBufferSize < CompressionBlockSize)
{
TempBufferSize = CompressionBlockSize;
TempBuffer = MakeUnique<uint8[]>(TempBufferSize);
}
if(ScratchBufferSize < ScrachSize)
{
ScratchBufferSize = ScrachSize;
ScratchBuffer = MakeUnique<uint8[]>(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.Get();
WorkingBuffers[1] = ScratchSpace.ScratchBuffer.Get() + 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 = ScratchSpace.TempBuffer.Get();
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)
{
bool bAllowed = true;
#if EXCLUDE_NONPAK_UE_EXTENSIONS
if ( PakFiles.Num() || UE_BUILD_SHIPPING)
{
FName Ext = FName(*FPaths::GetExtension(InFilename));
bAllowed = !ExcludedNonPakExtensions.Contains(Ext);
}
#endif
FFilenameSecurityDelegate& FilenameSecurityDelegate = GetFilenameSecurityDelegate();
if (bAllowed)
{
if (FilenameSecurityDelegate.IsBound())
{
bAllowed = FilenameSecurityDelegate.Execute(*InFilename);;
}
}
return bAllowed;
}
#if IS_PROGRAM
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);
}
}
#endif
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);
}
}
#if WITH_EDITOR
FPakFile::FPakFile(FArchive* Archive)
: bSigned(false)
, bIsValid(false)
{
Initialize(Archive);
}
#endif
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 (bSigned || FParse::Param(FCommandLine::Get(), TEXT("signedpak")) || FParse::Param(FCommandLine::Get(), TEXT("signed")))
{
if (!Decryptor)
{
Decryptor = MakeUnique<FChunkCacheWorker>(ReaderArchive, Filename);
}
ReaderArchive = new FSignedArchiveReader(ReaderArchive, Decryptor.Get());
}
}
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);
UE_CLOG((Info.bEncryptedIndex == 1) && (FPakPlatformFile::GetPakEncryptionKey() == nullptr), LogPakFile, Fatal, TEXT("Index of pak file '%s' is encrypted, but this executable doesn't have any valid decryption keys"), *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);
// Decrypt if necessary
if (Info.bEncryptedIndex)
{
DecryptData(IndexData.GetData(), Info.IndexSize);
}
// 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;
}
#if DO_CHECK
/**
* FThreadCheckingArchiveProxy - checks that inner archive is only used from the specified thread ID
*/
class FThreadCheckingArchiveProxy : public FArchiveProxy
{
public:
const uint32 ThreadId;
FArchive* InnerArchivePtr;
FThreadCheckingArchiveProxy(FArchive* InReader, uint32 InThreadId)
: FArchiveProxy(*InReader)
, ThreadId(InThreadId)
, InnerArchivePtr(InReader)
{}
virtual ~FThreadCheckingArchiveProxy()
{
if (InnerArchivePtr)
{
delete InnerArchivePtr;
}
}
//~ Begin FArchiveProxy Interface
virtual void Serialize(void* Data, int64 Length) override
{
if (FPlatformTLS::GetCurrentThreadId() != ThreadId)
{
UE_LOG(LogPakFile, Error, TEXT("Attempted serialize using thread-specific pak file reader on the wrong thread. Reader for thread %d used by thread %d."), ThreadId, FPlatformTLS::GetCurrentThreadId());
}
InnerArchive.Serialize(Data, Length);
}
virtual void Seek(int64 InPos) override
{
if (FPlatformTLS::GetCurrentThreadId() != ThreadId)
{
UE_LOG(LogPakFile, Error, TEXT("Attempted seek using thread-specific pak file reader on the wrong thread. Reader for thread %d used by thread %d."), ThreadId, FPlatformTLS::GetCurrentThreadId());
}
InnerArchive.Seek(InPos);
}
//~ End FArchiveProxy Interface
};
#endif //DO_CHECK
FArchive* FPakFile::GetSharedReader(IPlatformFile* LowerLevel)
{
uint32 Thread = FPlatformTLS::GetCurrentThreadId();
FArchive* PakReader = NULL;
{
FScopeLock ScopedLock(&CriticalSection);
TUniquePtr<FArchive>* ExistingReader = ReaderMap.Find(Thread);
if (ExistingReader)
{
PakReader = ExistingReader->Get();
}
}
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);
#if DO_CHECK
ReaderMap.Emplace(Thread, new FThreadCheckingArchiveProxy(PakReader, Thread));
#else //DO_CHECK
ReaderMap.Emplace(Thread, PakReader);
#endif //DO_CHECK
}
}
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 TUniquePtr<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 Mounted to %s"), *Pak.PakFile->GetFilename(), *Pak.PakFile->GetMountPoint());
}
}
#endif // !UE_BUILD_SHIPPING
FPakPlatformFile::FPakPlatformFile()
: LowerLevel(NULL)
, bSigned(false)
{
}
FPakPlatformFile::~FPakPlatformFile()
{
FCoreDelegates::OnMountPak.Unbind();
FCoreDelegates::OnUnmountPak.Unbind();
#if USE_PAK_PRECACHE
FPakPrecacher::Shutdown();
#endif
{
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
{
bool Result = false;
if (FPlatformProperties::RequiresCookedData() && !FParse::Param(CmdLine, TEXT("NoPak")))
{
TArray<FString> PakFolders;
GetPakFolders(CmdLine, PakFolders);
Result = CheckIfPakFilesExist(Inner, PakFolders);
}
return Result;
}
bool FPakPlatformFile::Initialize(IPlatformFile* Inner, const TCHAR* CmdLine)
{
// Inner is required.
check(Inner != NULL);
LowerLevel = Inner;
#if EXCLUDE_NONPAK_UE_EXTENSIONS
// Extensions for file types that should only ever be in a pak file. Used to stop unnecessary access to the lower level platform file
ExcludedNonPakExtensions.Add(TEXT("uasset"));
ExcludedNonPakExtensions.Add(TEXT("umap"));
ExcludedNonPakExtensions.Add(TEXT("ubulk"));
ExcludedNonPakExtensions.Add(TEXT("uexp"));
#endif
FEncryptionKey DecryptionKey;
FString PakSigningKeyExponent, PakSigningKeyModulus;
GetPakSigningKeys(PakSigningKeyExponent, PakSigningKeyModulus);
DecryptionKey.Exponent.Parse(PakSigningKeyExponent);
DecryptionKey.Modulus.Parse(PakSigningKeyModulus);
bSigned = !DecryptionKey.Exponent.IsZero() && !DecryptionKey.Modulus.IsZero();
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);
const bool bPreCookedNetwork = FParse::Param(FCommandLine::Get(), TEXT("precookednetwork") );
if (bPreCookedNetwork)
{
// precooked network builds are dependent on cook on the fly
check(bCookOnTheFly);
}
bMountPaks &= (!bCookOnTheFly || bPreCookedNetwork);
#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 = MakeUnique<FPakExec>(*this);
#endif // !UE_BUILD_SHIPPING
FCoreDelegates::OnMountPak.BindRaw(this, &FPakPlatformFile::HandleMountPakDelegate);
FCoreDelegates::OnUnmountPak.BindRaw(this, &FPakPlatformFile::HandleUnmountPakDelegate);
return !!LowerLevel;
}
void FPakPlatformFile::InitializeNewAsyncIO()
{
#if USE_PAK_PRECACHE
if (!WITH_EDITOR && FPlatformProcess::SupportsMultithreading() && !FParse::Param(FCommandLine::Get(), TEXT("FileOpenLog")))
{
FEncryptionKey DecryptionKey;
FString PakSigningKeyExponent, PakSigningKeyModulus;
GetPakSigningKeys(PakSigningKeyExponent, PakSigningKeyModulus);
DecryptionKey.Exponent.Parse(PakSigningKeyExponent);
DecryptionKey.Modulus.Parse(PakSigningKeyModulus);
FPakPrecacher::Init(LowerLevel, DecryptionKey);
}
else
{
UE_CLOG(FParse::Param(FCommandLine::Get(), TEXT("FileOpenLog")), LogPakFile, Display, TEXT("Disabled pak precacher to get an accurate load order. This should only be used to collect gameopenorder.log, as it is quite slow."));
GPakCache_Enable = 0;
}
#endif
}
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)
{
#if USE_PAK_PRECACHE
if (GPakCache_Enable)
{
FPakPrecacher::Get().Unmount(InPakFilename);
}
#endif
{
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);
}
else
{
if (IsNonPakFilenameAllowed(Filename))
{
// Default to wrapped file
Result = LowerLevel->OpenRead(Filename, bAllowWrite);
}
}
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, EPlatformFileRead ReadFlags, EPlatformFileWrite WriteFlags)
{
bool Result = false;
FPakFile* PakFile = NULL;
const FPakEntry* FileEntry = FindFileInPakFiles(From, &PakFile);
if (FileEntry != NULL)
{
// Copy from pak to LowerLevel->
// Create handles both files.
TUniquePtr<IFileHandle> DestHandle(LowerLevel->OpenWrite(To, false, (WriteFlags & EPlatformFileWrite::AllowRead) != EPlatformFileWrite::None));
TUniquePtr<IFileHandle> SourceHandle(CreatePakFileHandle(From, PakFile, FileEntry));
if (DestHandle && SourceHandle)
{
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, ReadFlags, WriteFlags);
}
return Result;
}
/**
* Module for the pak file
*/
class FPakFileModule : public IPlatformFileModule
{
public:
virtual IPlatformFile* GetPlatformFile() override
{
static TUniquePtr<IPlatformFile> AutoDestroySingleton = MakeUnique<FPakPlatformFile>();
return AutoDestroySingleton.Get();
}
};
IMPLEMENT_MODULE(FPakFileModule, PakFile);