mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-09 20:12:21 +02:00
404 lines
No EOL
15 KiB
C++
404 lines
No EOL
15 KiB
C++
#include "BFPlatform.h"
|
|
|
|
#ifdef BF_WWISE_ENABLED
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
//
|
|
// AkDefaultIOHookDeferred.cpp
|
|
//
|
|
// Default deferred low level IO hook (AK::StreamMgr::IAkIOHookDeferred)
|
|
// and file system (AK::StreamMgr::IAkFileLocationResolver) implementation
|
|
// on Windows.
|
|
//
|
|
// AK::StreamMgr::IAkFileLocationResolver:
|
|
// Resolves file location using simple path concatenation logic
|
|
// (implemented in ../Common/CAkFileLocationBase). It can be used as a
|
|
// standalone Low-Level IO system, or as part of a multi device system.
|
|
// In the latter case, you should manage multiple devices by implementing
|
|
// AK::StreamMgr::IAkFileLocationResolver elsewhere (you may take a look
|
|
// at class CAkDefaultLowLevelIODispatcher).
|
|
//
|
|
// AK::StreamMgr::IAkIOHookDeferred:
|
|
// Uses platform API for I/O. Calls to ::ReadFile() and ::WriteFile()
|
|
// do not block because files are opened with the FILE_FLAG_OVERLAPPED flag.
|
|
// Transfer completion is handled by internal FileIOCompletionRoutine function,
|
|
// which then calls the AkAIOCallback.
|
|
// The AK::StreamMgr::IAkIOHookDeferred interface is meant to be used with
|
|
// AK_SCHEDULER_DEFERRED_LINED_UP streaming devices.
|
|
//
|
|
// Init() creates a streaming device (by calling AK::StreamMgr::CreateDevice()).
|
|
// AkDeviceSettings::uSchedulerTypeFlags is set inside to AK_SCHEDULER_DEFERRED_LINED_UP.
|
|
// If there was no AK::StreamMgr::IAkFileLocationResolver previously registered
|
|
// to the Stream Manager, this object registers itself as the File Location Resolver.
|
|
//
|
|
// Copyright (c) 2006 Audiokinetic Inc. / All Rights Reserved
|
|
//
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "Common.h"
|
|
#include "AkDefaultIOHookDeferred.h"
|
|
#include <AK/SoundEngine/Common/AkMemoryMgr.h>
|
|
#include "AkFileHelpers.h"
|
|
|
|
|
|
// Device info.
|
|
#define WIN32_DEFERRED_DEVICE_NAME (L"Win32 Deferred") // Default deferred device name.
|
|
|
|
// With files opened with FILE_FLAG_NO_BUFFERING flag, accesses should be made in multiple of the
|
|
// sector size. We don't know what it is, so we choose a safe value of 2 KB.
|
|
// On Windows, real sector size can be queried with ::GetDiskFreeSpace() (but you need to know the drive letter).
|
|
#define WIN32_NO_BUFFERING_BLOCK_SIZE (2048)
|
|
|
|
|
|
AkMemPoolId CAkDefaultIOHookDeferred::m_poolID = AK_INVALID_POOL_ID;
|
|
|
|
CAkDefaultIOHookDeferred::CAkDefaultIOHookDeferred()
|
|
: m_deviceID( AK_INVALID_DEVICE_ID )
|
|
, m_bAsyncOpen( false )
|
|
{
|
|
}
|
|
|
|
CAkDefaultIOHookDeferred::~CAkDefaultIOHookDeferred()
|
|
{
|
|
}
|
|
|
|
// Initialization/termination. Init() registers this object as the one and
|
|
// only File Location Resolver if none were registered before. Then
|
|
// it creates a streaming device with scheduler type AK_SCHEDULER_DEFERRED_LINED_UP.
|
|
AKRESULT CAkDefaultIOHookDeferred::Init(
|
|
const AkDeviceSettings & in_deviceSettings, // Device settings.
|
|
bool in_bAsyncOpen/*=false*/ // If true, files are opened asynchronously when possible.
|
|
)
|
|
{
|
|
if ( in_deviceSettings.uSchedulerTypeFlags != AK_SCHEDULER_DEFERRED_LINED_UP )
|
|
{
|
|
AKASSERT( !"CAkDefaultIOHookDeferred I/O hook only works with AK_SCHEDULER_DEFERRED_LINED_UP devices" );
|
|
return AK_Fail;
|
|
}
|
|
|
|
m_bAsyncOpen = in_bAsyncOpen;
|
|
|
|
// If the Stream Manager's File Location Resolver was not set yet, set this object as the
|
|
// File Location Resolver (this I/O hook is also able to resolve file location).
|
|
if ( !AK::StreamMgr::GetFileLocationResolver() )
|
|
AK::StreamMgr::SetFileLocationResolver( this );
|
|
|
|
// Create a device in the Stream Manager, specifying this as the hook.
|
|
m_deviceID = AK::StreamMgr::CreateDevice( in_deviceSettings, this );
|
|
if ( m_deviceID != AK_INVALID_DEVICE_ID )
|
|
{
|
|
// Initialize structures needed to perform deferred transfers.
|
|
AkUInt32 uPoolSize = (AkUInt32)( in_deviceSettings.uMaxConcurrentIO * sizeof( OVERLAPPED ) );
|
|
m_poolID = AK::MemoryMgr::CreatePool( NULL, uPoolSize, sizeof( OVERLAPPED ), AkMalloc | AkFixedSizeBlocksMode );
|
|
if ( m_poolID == AK_INVALID_POOL_ID )
|
|
{
|
|
AKASSERT( !"Failed creating pool for asynchronous device" );
|
|
return AK_Fail;
|
|
}
|
|
AK_SETPOOLNAME( m_poolID, L"Deferred I/O hook" );
|
|
|
|
return AK_Success;
|
|
}
|
|
|
|
return AK_Fail;
|
|
}
|
|
|
|
void CAkDefaultIOHookDeferred::Term()
|
|
{
|
|
if ( AK::StreamMgr::GetFileLocationResolver() == this )
|
|
AK::StreamMgr::SetFileLocationResolver( NULL );
|
|
|
|
AK::StreamMgr::DestroyDevice( m_deviceID );
|
|
|
|
if ( m_poolID != AK_INVALID_POOL_ID )
|
|
{
|
|
AK::MemoryMgr::DestroyPool( m_poolID );
|
|
m_poolID = AK_INVALID_POOL_ID;
|
|
}
|
|
}
|
|
|
|
//
|
|
// IAkFileLocationAware implementation.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Returns a file descriptor for a given file name (string).
|
|
AKRESULT CAkDefaultIOHookDeferred::Open(
|
|
const AkOSChar* in_pszFileName, // File name.
|
|
AkOpenMode in_eOpenMode, // Open mode.
|
|
AkFileSystemFlags * in_pFlags, // Special flags. Can pass NULL.
|
|
bool & io_bSyncOpen, // If true, the file must be opened synchronously. Otherwise it is left at the File Location Resolver's discretion. Return false if Open needs to be deferred.
|
|
AkFileDesc & out_fileDesc // Returned file descriptor.
|
|
)
|
|
{
|
|
// We normally consider that calls to ::CreateFile() on a hard drive are fast enough to execute in the
|
|
// client thread. If you want files to be opened asynchronously when it is possible, this device should
|
|
// be initialized with the flag in_bAsyncOpen set to true.
|
|
if ( io_bSyncOpen || !m_bAsyncOpen )
|
|
{
|
|
io_bSyncOpen = true;
|
|
|
|
// Get the full file path, using path concatenation logic.
|
|
AkOSChar szFullFilePath[AK_MAX_PATH];
|
|
if ( GetFullFilePath( in_pszFileName, in_pFlags, in_eOpenMode, szFullFilePath ) == AK_Success )
|
|
{
|
|
// Open the file with FILE_FLAG_OVERLAPPED and FILE_FLAG_NO_BUFFERING flags.
|
|
AKRESULT eResult = CAkFileHelpers::OpenFile(
|
|
szFullFilePath,
|
|
in_eOpenMode,
|
|
true,
|
|
true,
|
|
out_fileDesc.hFile );
|
|
if ( eResult == AK_Success )
|
|
{
|
|
ULARGE_INTEGER Temp;
|
|
Temp.LowPart = ::GetFileSize( out_fileDesc.hFile,(LPDWORD)&Temp.HighPart );
|
|
|
|
out_fileDesc.iFileSize = Temp.QuadPart;
|
|
out_fileDesc.uSector = 0;
|
|
out_fileDesc.deviceID = m_deviceID;
|
|
out_fileDesc.pCustomParam = ( in_eOpenMode == AK_OpenModeRead ) ? NULL : (void*)1;
|
|
out_fileDesc.uCustomParamSize = 0;
|
|
}
|
|
return eResult;
|
|
}
|
|
|
|
return AK_Fail;
|
|
}
|
|
else
|
|
{
|
|
// The client allows us to perform asynchronous opening.
|
|
// We only need to specify the deviceID, and leave the boolean to false.
|
|
out_fileDesc.iFileSize = 0;
|
|
out_fileDesc.uSector = 0;
|
|
out_fileDesc.deviceID = m_deviceID;
|
|
out_fileDesc.pCustomParam = NULL;
|
|
out_fileDesc.uCustomParamSize = 0;
|
|
return AK_Success;
|
|
}
|
|
}
|
|
|
|
// Returns a file descriptor for a given file ID.
|
|
AKRESULT CAkDefaultIOHookDeferred::Open(
|
|
AkFileID in_fileID, // File ID.
|
|
AkOpenMode in_eOpenMode, // Open mode.
|
|
AkFileSystemFlags * in_pFlags, // Special flags. Can pass NULL.
|
|
bool & io_bSyncOpen, // If true, the file must be opened synchronously. Otherwise it is left at the File Location Resolver's discretion. Return false if Open needs to be deferred.
|
|
AkFileDesc & out_fileDesc // Returned file descriptor.
|
|
)
|
|
{
|
|
// We normally consider that calls to ::CreateFile() on a hard drive are fast enough to execute in the
|
|
// client thread. If you want files to be opened asynchronously when it is possible, this device should
|
|
// be initialized with the flag in_bAsyncOpen set to true.
|
|
if ( io_bSyncOpen || !m_bAsyncOpen )
|
|
{
|
|
io_bSyncOpen = true;
|
|
|
|
// Get the full file path, using path concatenation logic.
|
|
AkOSChar szFullFilePath[AK_MAX_PATH];
|
|
if ( GetFullFilePath( in_fileID, in_pFlags, in_eOpenMode, szFullFilePath ) == AK_Success )
|
|
{
|
|
// Open the file with FILE_FLAG_OVERLAPPED and FILE_FLAG_NO_BUFFERING flags.
|
|
AKRESULT eResult = CAkFileHelpers::OpenFile(
|
|
szFullFilePath,
|
|
in_eOpenMode,
|
|
true,
|
|
true,
|
|
out_fileDesc.hFile );
|
|
if ( eResult == AK_Success )
|
|
{
|
|
ULARGE_INTEGER Temp;
|
|
Temp.LowPart = ::GetFileSize( out_fileDesc.hFile,(LPDWORD)&Temp.HighPart );
|
|
|
|
out_fileDesc.iFileSize = Temp.QuadPart;
|
|
out_fileDesc.uSector = 0;
|
|
out_fileDesc.deviceID = m_deviceID;
|
|
out_fileDesc.pCustomParam = NULL;
|
|
out_fileDesc.uCustomParamSize = 0;
|
|
}
|
|
return eResult;
|
|
}
|
|
|
|
return AK_Fail;
|
|
}
|
|
else
|
|
{
|
|
// The client allows us to perform asynchronous opening.
|
|
// We only need to specify the deviceID, and leave the boolean to false.
|
|
out_fileDesc.iFileSize = 0;
|
|
out_fileDesc.uSector = 0;
|
|
out_fileDesc.deviceID = m_deviceID;
|
|
out_fileDesc.pCustomParam = NULL;
|
|
out_fileDesc.uCustomParamSize = 0;
|
|
return AK_Success;
|
|
}
|
|
}
|
|
|
|
//
|
|
// IAkIOHookDeferred implementation.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Local callback for overlapped I/O.
|
|
VOID CALLBACK CAkDefaultIOHookDeferred::FileIOCompletionRoutine(
|
|
DWORD dwErrorCode,
|
|
DWORD
|
|
#ifdef _DEBUG
|
|
dwNumberOfBytesTransfered
|
|
#endif
|
|
,
|
|
LPOVERLAPPED lpOverlapped
|
|
)
|
|
{
|
|
AkAsyncIOTransferInfo * pXferInfo = (AkAsyncIOTransferInfo*)(lpOverlapped->hEvent);
|
|
|
|
ReleaseOverlapped( lpOverlapped );
|
|
|
|
AKRESULT eResult = AK_Fail;
|
|
if ( ERROR_SUCCESS == dwErrorCode )
|
|
{
|
|
eResult = AK_Success;
|
|
AKASSERT( dwNumberOfBytesTransfered >= pXferInfo->uRequestedSize && dwNumberOfBytesTransfered <= pXferInfo->uBufferSize );
|
|
}
|
|
|
|
pXferInfo->pCallback( pXferInfo, eResult );
|
|
}
|
|
|
|
// Reads data from a file (asynchronous overload).
|
|
AKRESULT CAkDefaultIOHookDeferred::Read(
|
|
AkFileDesc & in_fileDesc, // File descriptor.
|
|
const AkIoHeuristics & /*in_heuristics*/, // Heuristics for this data transfer (not used in this implementation).
|
|
AkAsyncIOTransferInfo & io_transferInfo // Asynchronous data transfer info.
|
|
)
|
|
{
|
|
AKASSERT( in_fileDesc.hFile != INVALID_HANDLE_VALUE
|
|
&& io_transferInfo.uRequestedSize > 0
|
|
&& io_transferInfo.uBufferSize >= io_transferInfo.uRequestedSize );
|
|
|
|
// If this assert comes up, it might be beacause this hook's GetBlockSize() return value is incompatible
|
|
// with the system's handling of file reading for this specific file handle.
|
|
// If you are using the File Package extension, did you create your package with a compatible
|
|
// block size? It should be a multiple of WIN32_NO_BUFFERING_BLOCK_SIZE. (check -blocksize argument in the File Packager command line)
|
|
AKASSERT( ( io_transferInfo.uFilePosition % WIN32_NO_BUFFERING_BLOCK_SIZE ) == 0
|
|
|| !"Requested file position for I/O transfer is inconsistent with block size" );
|
|
|
|
OVERLAPPED * pOverlapped = GetFreeOverlapped( &io_transferInfo );
|
|
|
|
// Set file offset in OVERLAPPED structure.
|
|
pOverlapped->Offset = (DWORD)( io_transferInfo.uFilePosition & 0xFFFFFFFF );
|
|
pOverlapped->OffsetHigh = (DWORD)( ( io_transferInfo.uFilePosition >> 32 ) & 0xFFFFFFFF );
|
|
|
|
// File was open with asynchronous flag.
|
|
// Read overlapped.
|
|
// Note: With a file handle opened with FILE_FLAG_NO_BUFFERING, ::ReadFileEx() supports read sizes that go beyond the end
|
|
// of file. However, it does not support read sizes that are not a multiple of the drive's sector size.
|
|
// Since the buffer size is always a multiple of the block size, let's use io_transferInfo.uBufferSize
|
|
// instead of io_transferInfo.uRequestedSize.
|
|
if ( ::ReadFileEx( in_fileDesc.hFile,
|
|
io_transferInfo.pBuffer,
|
|
io_transferInfo.uBufferSize,
|
|
pOverlapped,
|
|
CAkDefaultIOHookDeferred::FileIOCompletionRoutine ) )
|
|
{
|
|
return AK_Success;
|
|
}
|
|
ReleaseOverlapped( pOverlapped );
|
|
return AK_Fail;
|
|
}
|
|
|
|
// Writes data to a file (asynchronous overload).
|
|
AKRESULT CAkDefaultIOHookDeferred::Write(
|
|
AkFileDesc & in_fileDesc, // File descriptor.
|
|
const AkIoHeuristics & /*in_heuristics*/, // Heuristics for this data transfer (not used in this implementation).
|
|
AkAsyncIOTransferInfo & io_transferInfo // Platform-specific asynchronous IO operation info.
|
|
)
|
|
{
|
|
AKASSERT( in_fileDesc.hFile != INVALID_HANDLE_VALUE
|
|
&& io_transferInfo.uRequestedSize > 0 );
|
|
|
|
// If this assert comes up, it might be beacause this hook's GetBlockSize() return value is incompatible
|
|
// with the system's handling of file reading for this specific file handle.
|
|
// Are you using the File Package Low-Level I/O with incompatible block size? (check -blocksize argument in the File Packager command line)
|
|
AKASSERT( io_transferInfo.uFilePosition % GetBlockSize( in_fileDesc ) == 0
|
|
|| !"Requested file position for I/O transfer is inconsistent with block size" );
|
|
|
|
OVERLAPPED * pOverlapped = GetFreeOverlapped( &io_transferInfo );
|
|
|
|
// Set file offset in OVERLAPPED structure.
|
|
pOverlapped->Offset = (DWORD)( io_transferInfo.uFilePosition & 0xFFFFFFFF );
|
|
pOverlapped->OffsetHigh = (DWORD)( ( io_transferInfo.uFilePosition >> 32 ) & 0xFFFFFFFF );
|
|
|
|
// File was open with asynchronous flag.
|
|
// Read overlapped.
|
|
if ( ::WriteFileEx( in_fileDesc.hFile,
|
|
io_transferInfo.pBuffer,
|
|
io_transferInfo.uRequestedSize,
|
|
pOverlapped,
|
|
CAkDefaultIOHookDeferred::FileIOCompletionRoutine ) )
|
|
{
|
|
return AK_Success;
|
|
}
|
|
ReleaseOverlapped( pOverlapped );
|
|
return AK_Fail;
|
|
}
|
|
|
|
// Cancel transfer(s).
|
|
void CAkDefaultIOHookDeferred::Cancel(
|
|
AkFileDesc & in_fileDesc, // File descriptor.
|
|
AkAsyncIOTransferInfo & /*io_transferInfo*/,// Transfer info to cancel (this implementation only handles "cancel all").
|
|
bool & io_bCancelAllTransfersForThisFile // Flag indicating whether all transfers should be cancelled for this file (see notes in function description).
|
|
)
|
|
{
|
|
if ( io_bCancelAllTransfersForThisFile )
|
|
{
|
|
CancelIo( in_fileDesc.hFile );
|
|
// Leave io_bCancelAllTransfersForThisFile to true: all transfers were cancelled for this file,
|
|
// so we don't need to be called again.
|
|
}
|
|
}
|
|
|
|
// Close a file.
|
|
AKRESULT CAkDefaultIOHookDeferred::Close(
|
|
AkFileDesc & in_fileDesc // File descriptor.
|
|
)
|
|
{
|
|
return CAkFileHelpers::CloseFile( in_fileDesc.hFile );
|
|
}
|
|
|
|
// Returns the block size for the file or its storage device.
|
|
AkUInt32 CAkDefaultIOHookDeferred::GetBlockSize(
|
|
AkFileDesc & in_fileDesc // File descriptor.
|
|
)
|
|
{
|
|
if ( in_fileDesc.pCustomParam == 0 )
|
|
return WIN32_NO_BUFFERING_BLOCK_SIZE;
|
|
return 1;
|
|
}
|
|
|
|
// Returns a description for the streaming device above this low-level hook.
|
|
void CAkDefaultIOHookDeferred::GetDeviceDesc(
|
|
AkDeviceDesc &
|
|
#ifndef AK_OPTIMIZED
|
|
out_deviceDesc // Description of associated low-level I/O device.
|
|
#endif
|
|
)
|
|
{
|
|
#ifndef AK_OPTIMIZED
|
|
AKASSERT( m_deviceID != AK_INVALID_DEVICE_ID || !"Low-Level device was not initialized" );
|
|
|
|
// Deferred scheduler.
|
|
out_deviceDesc.deviceID = m_deviceID;
|
|
out_deviceDesc.bCanRead = true;
|
|
out_deviceDesc.bCanWrite = true;
|
|
AKPLATFORM::SafeStrCpy( out_deviceDesc.szDeviceName, WIN32_DEFERRED_DEVICE_NAME, AK_MONITOR_DEVICENAME_MAXLENGTH );
|
|
out_deviceDesc.uStringSize = (AkUInt32)wcslen( out_deviceDesc.szDeviceName ) + 1;
|
|
#endif
|
|
}
|
|
|
|
// Returns custom profiling data: 1 if file opens are asynchronous, 0 otherwise.
|
|
// Tip: An interesting application for custom profiling data in deferred devices is to display
|
|
// the number of requests currently pending in the low-level IO.
|
|
AkUInt32 CAkDefaultIOHookDeferred::GetDeviceData()
|
|
{
|
|
return ( m_bAsyncOpen ) ? 1 : 0;
|
|
}
|
|
|
|
#endif |