FORCEINLINE FString BuildJsonStrFromMap(TMap<FString, FString> Map)
{
	FString JsonStr;
	TSharedRef< TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR> > > JsonWriter = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR> >::Create(&JsonStr);
	// Close the writer and finalize the output such that JsonStr has what we want
	TArray<FString> keys;
	Map.GetKeys(keys);
	JsonWriter->WriteObjectStart();
	for (int32 k = 0; k < Map.Num(); k++)
	{
		JsonWriter->WriteValue(keys[k], Map[keys[k]]);
	}
	JsonWriter->WriteObjectEnd();
	JsonWriter->Close();

	return JsonStr;
}
void FPhysxSharedData::DumpSharedMemoryUsage(FOutputDevice* Ar)
{
	struct FSharedResourceEntry
	{
		uint64 MemorySize;
		uint64 Count;
	};

	struct FSortBySize
	{
		FORCEINLINE bool operator()( const FSharedResourceEntry& A, const FSharedResourceEntry& B ) const 
		{ 
			// Sort descending
			return B.MemorySize < A.MemorySize;
		}
	};

	TMap<FString, FSharedResourceEntry> AllocationsByType;

	uint64 OverallSize = 0;
	int32 OverallCount = 0;

	TMap<FString, TArray<PxBase*> > ObjectsByType;

	for (int32 i=0; i < (int32)SharedObjects->getNbObjects(); ++i)
	{
		PxBase& Obj = SharedObjects->getObject(i);
		FString TypeName = ANSI_TO_TCHAR(Obj.getConcreteTypeName());

		TArray<PxBase*>* ObjectsArray = ObjectsByType.Find(TypeName);
		if (ObjectsArray == NULL)
		{
			ObjectsByType.Add(TypeName, TArray<PxBase*>());
			ObjectsArray = ObjectsByType.Find(TypeName);
		}

		check(ObjectsArray);
		ObjectsArray->Add(&Obj);
	}

	TArray<FString> TypeNames;
	ObjectsByType.GetKeys(TypeNames);

	for (int32 TypeIdx=0; TypeIdx < TypeNames.Num(); ++TypeIdx)
	{
		const FString& TypeName = TypeNames[TypeIdx];
		
		TArray<PxBase*>* ObjectsArray = ObjectsByType.Find(TypeName);
		check(ObjectsArray);

		PxSerializationRegistry* Sr = PxSerialization::createSerializationRegistry(*GPhysXSDK);
		PxCollection* Collection = PxCreateCollection();
		
		for (int32 i=0; i < ObjectsArray->Num(); ++i)
		{
			Collection->add(*((*ObjectsArray)[i]));;
		}

		PxSerialization::complete(*Collection, *Sr);	// chase all other stuff (shared shaps, materials, etc) needed to serialize this collection

		FPhysXCountMemoryStream Out;
		PxSerialization::serializeCollectionToBinary(Out, *Collection, *Sr);

		Collection->release();
		Sr->release();

		OverallSize += Out.UsedMemory;
		OverallCount += ObjectsArray->Num();

		FSharedResourceEntry NewEntry;
		NewEntry.Count = ObjectsArray->Num();
		NewEntry.MemorySize = Out.UsedMemory;

		AllocationsByType.Add(TypeName, NewEntry);
	}

	Ar->Logf(TEXT(""));
	Ar->Logf(TEXT("Shared Resources:"));
	Ar->Logf(TEXT(""));

	AllocationsByType.ValueSort(FSortBySize());
	
	Ar->Logf(TEXT("%-10d %s (%d)"), OverallSize, TEXT("Overall"), OverallCount );
	
	for( auto It=AllocationsByType.CreateConstIterator(); It; ++It )
	{
		Ar->Logf(TEXT("%-10d %s (%d)"), It.Value().MemorySize, *It.Key(), It.Value().Count );
	}
}
void FPaperFlipbookHelpers::ExtractFlipbooksFromSprites(TMap<FString, TArray<UPaperSprite*> >& OutSpriteFlipbookMap, const TArray<UPaperSprite*>& Sprites, const TArray<FString>& InSpriteNames)
{
	OutSpriteFlipbookMap.Reset();

	// Local copy
	check((InSpriteNames.Num() == 0) || (InSpriteNames.Num() == Sprites.Num()));
	TArray<FString> SpriteNames = InSpriteNames;
	if (InSpriteNames.Num() == 0)
	{
		SpriteNames.Reset();
		for (int32 SpriteIndex = 0; SpriteIndex < Sprites.Num(); ++SpriteIndex)
		{
			check(Sprites[SpriteIndex] != nullptr);
			SpriteNames.Add(Sprites[SpriteIndex]->GetName());
		}
	}

	// Group them
	TMap<FString, UPaperSprite*> SpriteNameMap;
	TArray<UPaperSprite*> RemainingSprites;

	for (int32 SpriteIndex = 0; SpriteIndex < Sprites.Num(); ++SpriteIndex)
	{
		UPaperSprite* Sprite = Sprites[SpriteIndex];
		const FString SpriteName = SpriteNames[SpriteIndex];

		SpriteNameMap.Add(SpriteName, Sprite);

		int32 SpriteNumber = 0;
		FString SpriteBareString;
		if (ExtractSpriteNumber(SpriteName, /*out*/ SpriteBareString, /*out*/ SpriteNumber))
		{
			SpriteBareString = ObjectTools::SanitizeObjectName(SpriteBareString);
			OutSpriteFlipbookMap.FindOrAdd(SpriteBareString).Add(Sprite);
		}
		else
		{
			RemainingSprites.Add(Sprite);
		}
	}

	// Natural sort using the same method as above
	struct FSpriteSortPredicate
	{
		FSpriteSortPredicate() {}

		// Sort predicate operator
		bool operator()(UPaperSprite& LHS, UPaperSprite& RHS) const
		{
			FString LeftString;
			int32 LeftNumber;
			ExtractSpriteNumber(LHS.GetName(), /*out*/ LeftString, /*out*/ LeftNumber);

			FString RightString;
			int32 RightNumber;
			ExtractSpriteNumber(RHS.GetName(), /*out*/ RightString, /*out*/ RightNumber);

			return (LeftString == RightString) ? (LeftNumber < RightNumber) : (LeftString < RightString);
		}
	};

	// Sort sprites
	TArray<FString> Keys;
	OutSpriteFlipbookMap.GetKeys(Keys);
	for (auto SpriteName : Keys)
	{
		OutSpriteFlipbookMap[SpriteName].Sort(FSpriteSortPredicate());
	}

	// Create a flipbook from all remaining sprites
	// Not sure if this is desirable behavior, might want one flipbook per sprite
	if (RemainingSprites.Num() > 0)
	{
		RemainingSprites.Sort(FSpriteSortPredicate());

		const FString DesiredName = GetCleanerSpriteName(RemainingSprites[0]->GetName()) + TEXT("_Flipbook");
		const FString SanitizedName = ObjectTools::SanitizeObjectName(DesiredName);

		OutSpriteFlipbookMap.Add(SanitizedName, RemainingSprites);
	}
}