/***********************************************************************
THIS FILE IS AUTOMATICALLY GENERATED. DO NOT MODIFY
DEVELOPER: Zihan Chen(vczh)
***********************************************************************/
#include "Vlpp.h"
/***********************************************************************
.\LOCALE.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_LOCALE
#define VCZH_LOCALE
namespace vl
{
/***********************************************************************
Locale
***********************************************************************/
/// Locale awared operations. Macro "INVLOC" is a shortcut to get a invariant locale.
///
///
/// For all string operations that the normalization does not set to None,
/// and all non-string operations,
/// the result is platform-dependent.
/// This class is designed to process human-readable text,
/// do not rely on the result.
///
///
/// In Linux and macOS, only en-US is supported, with a hard-coded set of date and time formats,
/// and string operations only support None and IgnoreCase for normalization.
///
///
class Locale : public Object
{
protected:
WString localeName;
public:
/// Create a locale with a specified local name.
/// The name of the locale. If it is not provided, it becomes the invariant locale.
///
/// In Windows, the specified locale need to be installed in order to take effect.
/// In Linux and macOS, only en-US is supported.
///
Locale() = default;
Locale(const Locale&) = default;
Locale(Locale&&) = default;
~Locale() = default;
Locale& operator=(const Locale&) = default;
Locale& operator=(Locale&&) = default;
Locale(const WString& _localeName);
std::strong_ordering operator<=>(const Locale& locale)const { return localeName <=> locale.localeName; }
bool operator==(const Locale& locale)const { return localeName == locale.localeName; }
/// Get the invariant locale. An invariant locale is neutral, it is not awared of any language specified thing.
/// The invariant locale.
static Locale Invariant();
/// Get the system default locale. This locale controls the code page that used by the the system to interpret ANSI string buffers.
/// The system default locale.
static Locale SystemDefault();
/// Get the user default locale. This locale reflect the user's settings and UI language.
/// The user default locale.
static Locale UserDefault();
/// Get all supported locales.
/// All supported locales.
static void Enumerate(collections::List& locales);
/// Get the name of this locale.
/// The name of this locale.
const WString& GetName()const;
/// Get all short date formats for this locale.
/// Returns all formats.
void GetShortDateFormats(collections::List& formats)const;
/// Get all long date formats for this locale.
/// Returns all formats.
void GetLongDateFormats(collections::List& formats)const;
/// Get all Year-Month date formats for this locale.
/// Returns all formats.
void GetYearMonthDateFormats(collections::List& formats)const;
/// Get all long time formats for this locale.
/// Returns all formats.
void GetLongTimeFormats(collections::List& formats)const;
/// Get all short time formats for this locale.
/// Returns all formats.
void GetShortTimeFormats(collections::List& formats)const;
/// Convert a date to a formatted string.
/// The formatted string.
/// The format to use.
/// The date to convert.
///
/// The value of the "format" argument must come from any of the following functions.
/// Otherwise the behavior is undefined.
///
///
WString FormatDate(const WString& format, DateTime date)const;
/// Convert a time to a formatted string.
/// The formatted string.
/// The format to use.
/// The time to convert.
///
/// The value of the "format" argument must come from any of the following functions.
/// Otherwise the behavior is undefined.
///
///
WString FormatTime(const WString& format, DateTime time)const;
/// Convert a number to a formatted string according to the locale.
/// The formatted string.
/// The number to convert.
WString FormatNumber(const WString& number)const;
/// Convert a currency (money) to a formatted string according to the locale.
/// The formatted string.
/// The currency to convert.
WString FormatCurrency(const WString& currency)const;
/// Get the short display string of a day of week according to the locale.
/// The display string.
/// Day of week, begins from 0 as Sunday.
WString GetShortDayOfWeekName(vint dayOfWeek)const;
/// Get the long display string of a day of week according to the locale.
/// The display string.
/// Day of week, begins from 0 as Sunday.
WString GetLongDayOfWeekName(vint dayOfWeek)const;
/// Get the short display string of a month according to the locale.
/// The display string.
/// Month, begins from 1 as January.
WString GetShortMonthName(vint month)const;
/// Get the long display string of a month according to the locale.
/// The display string.
/// Month, begins from 1 as January.
WString GetLongMonthName(vint month)const;
#ifdef VCZH_MSVC
/// Convert characters to the full width.
/// The converted string.
/// The string to convert.
/// This function is only available in Windows.
WString ToFullWidth(const WString& str)const;
/// Convert characters to the half width.
/// The converted string.
/// The string to convert.
/// This function is only available in Windows.
WString ToHalfWidth(const WString& str)const;
/// Convert characters to the Hiragana.
/// The converted string.
/// The string to convert.
/// This function is only available in Windows.
WString ToHiragana(const WString& str)const;
/// Convert characters to the Katagana.
/// The converted string.
/// The string to convert.
/// This function is only available in Windows.
WString ToKatagana(const WString& str)const;
#endif
/// Convert characters to the lower case using the file system rule.
/// The converted string.
/// The string to convert.
WString ToLower(const WString& str)const;
/// Convert characters to the upper case using the file system rule.
/// The converted string.
/// The string to convert.
WString ToUpper(const WString& str)const;
/// Convert characters to the lower case using the linguistic rule.
/// The converted string.
/// The string to convert.
WString ToLinguisticLower(const WString& str)const;
/// Convert characters to the upper case using the linguistic rule.
/// The converted string.
/// The string to convert.
WString ToLinguisticUpper(const WString& str)const;
#ifdef VCZH_MSVC
/// Convert characters to Simplified Chinese.
/// The converted string.
/// The string to convert.
/// This function is only available in Windows.
WString ToSimplifiedChinese(const WString& str)const;
/// Convert characters to the Traditional Chinese.
/// The converted string.
/// The string to convert.
/// This function is only available in Windows.
WString ToTraditionalChinese(const WString& str)const;
/// Convert characters to the tile case, in which the first letter of each major word is capitalized.
/// The converted string.
/// The string to convert.
/// This function is only available in Windows.
WString ToTileCase(const WString& str)const;
#endif
/// Mergable flags controlling how to normalize a string.
enum Normalization
{
/// Do nothing.
None=0,
/// Ignore case using the file system rule.
IgnoreCase=1,
#ifdef VCZH_MSVC
/// Ignore case using the linguistic rule. This value is only available in Windows.
IgnoreCaseLinguistic=2,
/// Ignore the difference between between hiragana and katakana characters. This value is only available in Windows.
IgnoreKanaType=4,
/// Ignore nonspacing characters. This value is only available in Windows.
IgnoreNonSpace=8,
/// Ignore symbols and punctuation. This value is only available in Windows.
IgnoreSymbol=16,
/// Ignore the difference between half-width and full-width characters. This value is only available in Windows.
IgnoreWidth=32,
/// Treat digits as numbers during sorting. This value is only available in Windows.
DigitsAsNumbers=64,
/// Treat punctuation the same as symbols. This value is only available in Windows.
StringSoft=128,
#endif
};
/// Compare two strings.
/// Returns 0 if two strings are equal. Returns a positive number if the first string is larger. Returns a negative number if the second string is larger. When sorting strings, larger strings are put after then smaller strings.
/// The first string to compare.
/// The second string to compare.
/// Flags controlling how to normalize a string.
vint Compare(const WString& s1, const WString& s2, Normalization normalization)const;
/// Compare two strings to test binary equivalence.
/// Returns 0 if two strings are equal. Returns a positive number if the first string is larger. Returns a negative number if the second string is larger. When sorting strings, larger strings are put after then smaller strings.
/// The first string to compare.
/// The second string to compare.
vint CompareOrdinal(const WString& s1, const WString& s2)const;
/// Compare two strings to test binary equivalence, ignoring case.
/// Returns 0 if two strings are equal. Returns a positive number if the first string is larger. Returns a negative number if the second string is larger. When sorting strings, larger strings are put after then smaller strings.
/// The first string to compare.
/// The second string to compare.
vint CompareOrdinalIgnoreCase(const WString& s1, const WString& s2)const;
/// Find the first position that the sub string appears in a text.
/// Returns a pair of numbers, the first number indicating the position in the text, the second number indicating the size of the equivalence sub string in the text.
/// The text to find the sub string.
/// The sub string to match.
/// Flags controlling how to normalize a string.
/// For any normalization that is not None, the found sub string could be different to the string you want to find.
collections::Pair FindFirst(const WString& text, const WString& find, Normalization normalization)const;
/// Find the last position that the sub string appears in a text.
/// Returns a pair of numbers, the first number indicating the position in the text, the second number indicating the size of the equivalence sub string in the text.
/// The text to find the sub string.
/// The sub string to match.
/// Flags controlling how to normalize a string.
/// For any normalization that is not None, the found sub string could be different to the string you want to find.
collections::Pair FindLast(const WString& text, const WString& find, Normalization normalization)const;
/// Test is the prefix of the text equivalence to the provided sub string.
/// Returns true if the prefix of the text equivalence to the provided sub string.
/// The text to test the prefix.
/// The sub string to match.
/// Flags controlling how to normalize a string.
/// For any normalization that is not None, the found prefix could be different to the string you want to find.
bool StartsWith(const WString& text, const WString& find, Normalization normalization)const;
/// Test is the postfix of the text equivalence to the provided sub string.
/// Returns true if the postfix of the text equivalence to the provided sub string.
/// The text to test the postfix.
/// The sub string to match.
/// Flags controlling how to normalize a string.
/// For any normalization that is not None, the postfix could be different to the string you want to find.
bool EndsWith(const WString& text, const WString& find, Normalization normalization)const;
};
#define INVLOC vl::Locale::Invariant()
/***********************************************************************
ILocaleImpl
***********************************************************************/
/// Platform-specific locale implementation interface.
class ILocaleImpl : public virtual feature_injection::IFeatureImpl
{
public:
virtual Locale Invariant() const = 0;
virtual Locale SystemDefault() const = 0;
virtual Locale UserDefault() const = 0;
virtual void Enumerate(collections::List& locales) const = 0;
virtual void GetShortDateFormats(const WString& localeName, collections::List& formats) const = 0;
virtual void GetLongDateFormats(const WString& localeName, collections::List& formats) const = 0;
virtual void GetYearMonthDateFormats(const WString& localeName, collections::List& formats) const = 0;
virtual void GetLongTimeFormats(const WString& localeName, collections::List& formats) const = 0;
virtual void GetShortTimeFormats(const WString& localeName, collections::List& formats) const = 0;
virtual WString FormatDate(const WString& localeName, const WString& format, DateTime date) const = 0;
virtual WString FormatTime(const WString& localeName, const WString& format, DateTime time) const = 0;
virtual WString FormatNumber(const WString& localeName, const WString& number) const = 0;
virtual WString FormatCurrency(const WString& localeName, const WString& currency) const = 0;
virtual WString GetShortDayOfWeekName(const WString& localeName, vint dayOfWeek) const = 0;
virtual WString GetLongDayOfWeekName(const WString& localeName, vint dayOfWeek) const = 0;
virtual WString GetShortMonthName(const WString& localeName, vint month) const = 0;
virtual WString GetLongMonthName(const WString& localeName, vint month) const = 0;
virtual WString ToLower(const WString& localeName, const WString& str) const = 0;
virtual WString ToUpper(const WString& localeName, const WString& str) const = 0;
virtual WString ToLinguisticLower(const WString& localeName, const WString& str) const = 0;
virtual WString ToLinguisticUpper(const WString& localeName, const WString& str) const = 0;
virtual vint Compare(const WString& localeName, const WString& s1, const WString& s2, Locale::Normalization normalization) const = 0;
virtual vint CompareOrdinal(const WString& s1, const WString& s2) const = 0;
virtual vint CompareOrdinalIgnoreCase(const WString& s1, const WString& s2) const = 0;
virtual collections::Pair FindFirst(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const = 0;
virtual collections::Pair FindLast(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const = 0;
virtual bool StartsWith(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const = 0;
virtual bool EndsWith(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const = 0;
};
extern void InjectLocaleImpl(ILocaleImpl* impl);
extern void EjectLocaleImpl(ILocaleImpl* impl);
/***********************************************************************
EnUsLocaleImpl
***********************************************************************/
/// A platform independent implementation that only supports en-US.
class EnUsLocaleImpl : public feature_injection::FeatureImpl
{
public:
Locale Invariant() const override;
Locale SystemDefault() const override;
Locale UserDefault() const override;
void Enumerate(collections::List& locales) const override;
void GetShortDateFormats(const WString& localeName, collections::List& formats) const override;
void GetLongDateFormats(const WString& localeName, collections::List& formats) const override;
void GetYearMonthDateFormats(const WString& localeName, collections::List& formats) const override;
void GetLongTimeFormats(const WString& localeName, collections::List& formats) const override;
void GetShortTimeFormats(const WString& localeName, collections::List& formats) const override;
WString FormatDate(const WString& localeName, const WString& format, DateTime date) const override;
WString FormatTime(const WString& localeName, const WString& format, DateTime time) const override;
WString FormatNumber(const WString& localeName, const WString& number) const override;
WString FormatCurrency(const WString& localeName, const WString& currency) const override;
WString GetShortDayOfWeekName(const WString& localeName, vint dayOfWeek) const override;
WString GetLongDayOfWeekName(const WString& localeName, vint dayOfWeek) const override;
WString GetShortMonthName(const WString& localeName, vint month) const override;
WString GetLongMonthName(const WString& localeName, vint month) const override;
WString ToLower(const WString& localeName, const WString& str) const override;
WString ToUpper(const WString& localeName, const WString& str) const override;
WString ToLinguisticLower(const WString& localeName, const WString& str) const override;
WString ToLinguisticUpper(const WString& localeName, const WString& str) const override;
vint Compare(const WString& localeName, const WString& s1, const WString& s2, Locale::Normalization normalization) const override;
vint CompareOrdinal(const WString& s1, const WString& s2) const override;
vint CompareOrdinalIgnoreCase(const WString& s1, const WString& s2) const override;
collections::Pair FindFirst(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override;
collections::Pair FindLast(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override;
bool StartsWith(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override;
bool EndsWith(const WString& localeName, const WString& text, const WString& find, Locale::Normalization normalization) const override;
};
}
#endif
/***********************************************************************
.\THREADING.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_THREADING
#define VCZH_THREADING
namespace vl
{
/***********************************************************************
Kernel Mode Objects
***********************************************************************/
namespace threading_internal
{
struct WaitableData;
struct ThreadData;
struct MutexData;
struct SemaphoreData;
struct EventData;
struct CriticalSectionData;
struct ReaderWriterLockData;
struct ConditionVariableData;
}
/// Base type of all synchronization objects.
class WaitableObject : public Object
{
#if defined VCZH_MSVC
private:
threading_internal::WaitableData* waitableData;
protected:
WaitableObject();
void SetData(threading_internal::WaitableData* data);
public:
NOT_COPYABLE(WaitableObject);
/// Test if the object has already been created. Some of the synchronization objects should initialize itself after the constructor.
/// Returns true if the object has already been created.
/// This function is only available in Windows.
bool IsCreated();
/// Wait for this object to signal.
/// Returns true if the object is signaled. Returns false if this operation failed.
bool Wait();
/// Wait for this object to signal for a period of time.
/// Returns true if the object is signaled. Returns false if this operation failed, including time out.
/// Time in milliseconds.
/// This function is only available in Windows.
bool WaitForTime(vint ms);
/// Wait for multiple objects.
/// Returns true if all objects are signaled. Returns false if this operation failed.
/// A pointer to an array to pointers.
/// The number of objects in the array.
/// This function is only available in Windows.
static bool WaitAll(WaitableObject** objects, vint count);
/// Wait for multiple objects for a period of time.
/// Returns true if all objects are signaled. Returns false if this operation failed, including time out.
/// A pointer to an array to pointers.
/// The number of objects in the array.
/// Time in milliseconds.
/// This function is only available in Windows.
static bool WaitAllForTime(WaitableObject** objects, vint count, vint ms);
/// Wait for one of the objects.
/// Returns the index of the first signaled or abandoned object, according to the "abandoned" parameter. Returns -1 if this operation failed.
/// A pointer to an array to pointers.
/// The number of objects in the array.
/// Returns true if the waiting is canceled by an abandoned object. An abandoned object is caused by it's owner thread existing without releasing it.
/// This function is only available in Windows.
static vint WaitAny(WaitableObject** objects, vint count, bool* abandoned);
/// Wait for one of the objects for a period of time.
/// Returns the index of the first signaled or abandoned object, according to the "abandoned" parameter. Returns -1 if this operation failed, including time out.
/// A pointer to an array to pointers.
/// The number of objects in the array.
/// Time in milliseconds.
/// Returns true if the waiting is canceled by an abandoned object. An abandoned object is caused by it's owner thread existing without releasing it.
/// This function is only available in Windows.
static vint WaitAnyForTime(WaitableObject** objects, vint count, vint ms, bool* abandoned);
#elif defined VCZH_GCC
virtual bool Wait() = 0;
#endif
};
/// Thread. [M:vl.Thread.CreateAndStart] is the suggested way to create threads.
class Thread : public WaitableObject
{
friend void InternalThreadProc(Thread* thread);
public:
/// Thread state.
enum ThreadState
{
/// The thread has not started.
NotStarted,
/// The thread is running.
Running,
/// The thread has been stopped.
Stopped
};
typedef void(*ThreadProcedure)(Thread*, void*);
protected:
threading_internal::ThreadData* internalData;
volatile ThreadState threadState;
virtual void Run()=0;
Thread();
public:
~Thread();
/// Create a thread using a function pointer.
/// Returns the created thread.
/// The function pointer.
/// The argument to call the function pointer.
/// Set to true (by default) to make the thread delete itself after the job is done. If you set this argument to true, you are not recommended to touch the returned thread pointer in any way.
static Thread* CreateAndStart(ThreadProcedure procedure, void* argument=0, bool deleteAfterStopped=true);
/// Create a thread using a function object or a lambda expression.
/// Returns the created thread.
/// The function object or the lambda expression.
/// Set to true (by default) to make the thread delete itself after the job is done. If you set this argument to true, you are not recommended to touch the returned thread pointer in any way.
static Thread* CreateAndStart(const Func& procedure, bool deleteAfterStopped=true);
/// Pause the caller thread for a period of time.
/// Time in milliseconds.
static void Sleep(vint ms);
/// Get the number of logical processors.
/// The number of logical processor.
static vint GetCPUCount();
/// Get the current thread id.
/// The current thread id.
static vint GetCurrentThreadId();
/// Start the thread.
/// Returns true if this operation succeeded.
bool Start();
#if defined VCZH_GCC
bool Wait();
#endif
/// Stop the thread.
/// Returns true if this operation succeeded.
bool Stop();
/// Get the state of the thread.
/// The state of the thread.
ThreadState GetState();
#ifdef VCZH_MSVC
void SetCPU(vint index);
#endif
};
/// Mutex. or is required to initialize a mutex.
class Mutex : public WaitableObject
{
private:
threading_internal::MutexData* internalData;
public:
Mutex();
~Mutex();
/// Create a mutex.
/// Returns true if this operation succeeded.
/// Set to true to own the created mutex.
/// Name of the mutex. If it is not empty, than it is a global named mutex. This argument is ignored in Linux.
bool Create(bool owned=false, const WString& name=L"");
/// Open an existing global named mutex.
/// Returns true if this operation succeeded.
/// Set to true make the mutex visible to all all child processes. This argument is only used in Windows.
/// Name of the mutex.
bool Open(bool inheritable, const WString& name);
///
/// Release the mutex.
/// In Linux, calling Release() more than once between two Wait(), or calling Wait() more than once between two Release(), will results in an undefined behavior.
///
/// Returns true if this operation succeeded.
bool Release();
#ifdef VCZH_GCC
bool Wait();
#endif
};
/// Semaphore. or is required to initialize a semaphore.
class Semaphore : public WaitableObject
{
private:
threading_internal::SemaphoreData* internalData;
public:
Semaphore();
~Semaphore();
/// Create a semaphore.
/// Returns true if this operation succeeded.
/// Define the counter of the semaphore.
/// Define the maximum value of the counter of the semaphore. This argument is only used in Windows.
/// Name of the semaphore. If it is not empty, than it is a global named semaphore. This argument is ignored in Linux.
bool Create(vint initialCount, vint maxCount, const WString& name=L"");
/// Open an existing global named semaphore.
/// Returns true if this operation succeeded.
/// Set to true make the semaphore visible to all all child processes. This argument is only used in Windows.
/// Name of the semaphore.
bool Open(bool inheritable, const WString& name);
/// Release the semaphore once.
/// Returns true if this operation succeeded.
bool Release();
/// Release the semaphore multiple times.
/// Returns true if this operation succeeded.
/// The amout to release.
vint Release(vint count);
#ifdef VCZH_GCC
bool Wait();
#endif
};
/// Event. or is required to initialize an event.
class EventObject : public WaitableObject
{
private:
threading_internal::EventData* internalData;
public:
EventObject();
~EventObject();
/// Create an auto unsignal event. Auto unsignal means, when one thread waits for the event and succeeded, the event will become unsignaled immediately.
/// Returns true if this operation succeeded.
/// Set to true make the event signaled at the beginning.
/// Name of the event. If it is not empty, than it is a global named mutex. This argument is only used in Windows.
bool CreateAutoUnsignal(bool signaled, const WString& name=L"");
/// Create a manual unsignal event.
/// Returns true if this operation succeeded.
/// Set to true make the event signaled at the beginning.
/// Name of the event. If it is not empty, than it is a global named mutex. This argument is only used in Windows.
bool CreateManualUnsignal(bool signaled, const WString& name=L"");
/// Open an existing global named event.
/// Returns true if this operation succeeded.
/// Set to true make the event visible to all all child processes. This argument is only used in Windows.
/// Name of the event. This argument is only used in Windows.
bool Open(bool inheritable, const WString& name);
/// Signal the event.
/// Returns true if this operation succeeded.
bool Signal();
/// Unsignal the event.
/// Returns true if this operation succeeded.
bool Unsignal();
#ifdef VCZH_GCC
bool Wait();
#endif
};
/***********************************************************************
Thread Pool
***********************************************************************/
/// A light-weight thread pool.
class ThreadPoolLite : public Object
{
private:
ThreadPoolLite();
~ThreadPoolLite();
public:
/// Queue a function pointer.
/// Returns true if this operation succeeded.
/// The function pointer.
/// The argument to call the function pointer.
static bool Queue(void(*proc)(void*), void* argument);
/// Queue a function object.
/// Returns true if this operation succeeded.
/// The function object.
static bool Queue(const Func& proc);
/// Queue a lambda expression.
/// The type of the lambda expression.
/// The lambda expression.
template
static void QueueLambda(const T& proc)
{
Queue(Func(proc));
}
#ifdef VCZH_GCC
static bool Stop(bool discardPendingTasks);
#endif
};
/***********************************************************************
Kernel Mode Objects in Process
***********************************************************************/
///
/// Critical section.
/// The macro "CS_LOCK" is recommended instead of calling [M:vl.CriticalSection.Enter] and [M:vl.CriticalSection.Leave] like this:
///
///
///
/// In Windows, enter a owned critical section will not result in dead lock.
/// In Linux and macOS, it works like a mutex.
///
class CriticalSection : public Object
{
private:
friend class ConditionVariable;
threading_internal::CriticalSectionData* internalData;
public:
NOT_COPYABLE(CriticalSection);
/// Create a critical section.
CriticalSection();
~CriticalSection();
/// Try enter a critical section. This function will return immediately.
/// Returns true if the current thread owned the critical section.
bool TryEnter();
/// Enter a critical section.
void Enter();
/// Leave a critical section.
void Leave();
public:
class Scope : public Object
{
private:
CriticalSection* criticalSection;
public:
NOT_COPYABLE(Scope);
Scope(CriticalSection& _criticalSection);
~Scope();
};
};
///
/// Reader writer lock.
/// The macro "READER_LOCK" and "WRITER_LOCK" are recommended instead of calling [M:vl.ReaderWriterLock.EnterReader], [M:vl.ReaderWriterLock.LeaveReader], [M:vl.ReaderWriterLock.EnterWriter] and [M:vl.ReaderWriterLock.LeaveWriter] like this:
///
/// or
///
///
class ReaderWriterLock : public Object
{
private:
friend class ConditionVariable;
threading_internal::ReaderWriterLockData* internalData;
public:
NOT_COPYABLE(ReaderWriterLock);
/// Create a reader writer lock.
ReaderWriterLock();
~ReaderWriterLock();
/// Try acquire a reader lock. This function will return immediately.
/// Returns true if the current thread acquired the reader lock.
bool TryEnterReader();
/// Acquire a reader lock.
void EnterReader();
/// Release a reader lock.
void LeaveReader();
/// Try acquire a writer lock. This function will return immediately.
/// Returns true if the current thread acquired the writer lock.
bool TryEnterWriter();
/// Acquire a writer lock.
void EnterWriter();
/// Release a writer lock.
void LeaveWriter();
public:
class ReaderScope : public Object
{
private:
ReaderWriterLock* lock;
public:
NOT_COPYABLE(ReaderScope);
ReaderScope(ReaderWriterLock& _lock);
~ReaderScope();
};
class WriterScope : public Object
{
private:
ReaderWriterLock* lock;
public:
NOT_COPYABLE(WriterScope);
WriterScope(ReaderWriterLock& _lock);
~WriterScope();
};
};
/// Conditional variable.
class ConditionVariable : public Object
{
private:
threading_internal::ConditionVariableData* internalData;
public:
NOT_COPYABLE(ConditionVariable);
/// Create a conditional variable.
ConditionVariable();
~ConditionVariable();
/// Bind a conditional variable with a owned critical section and release it. When the function returns, the condition variable is activated, and the current thread owned the critical section again.
/// Returns true if this operation succeeded.
/// The critical section.
bool SleepWith(CriticalSection& cs);
#ifdef VCZH_MSVC
/// Bind a conditional variable with a owned critical section and release it for a period of time. When the function returns, the condition variable is activated or it is time out, and the current thread owned the critical section again.
/// Returns true if this operation succeeded.
/// The critical section.
/// Time in milliseconds.
/// This function is only available in Windows.
bool SleepWithForTime(CriticalSection& cs, vint ms);
/// Bind a conditional variable with a owned reader lock and release it. When the function returns, the condition variable is activated, and the current thread owned the reader lock again.
/// Returns true if this operation succeeded.
/// The reader lock.
/// This function is only available in Windows.
bool SleepWithReader(ReaderWriterLock& lock);
/// Bind a conditional variable with a owned reader lock and release it for a period of time. When the function returns, the condition variable is activated or it is time out, and the current thread owned the reader lock again.
/// Returns true if this operation succeeded.
/// The reader lock.
/// Time in milliseconds.
/// This function is only available in Windows.
bool SleepWithReaderForTime(ReaderWriterLock& lock, vint ms);
/// Bind a conditional variable with a owned writer lock and release it. When the function returns, the condition variable is activated, and the current thread owned the writer lock again.
/// Returns true if this operation succeeded.
/// The writer lock.
/// This function is only available in Windows.
bool SleepWithWriter(ReaderWriterLock& lock);
/// Bind a conditional variable with a owned writer lock and release it for a period of time. When the function returns, the condition variable is activated or it is time out, and the current thread owned the writer lock again.
/// Returns true if this operation succeeded.
/// The writer lock.
/// Time in milliseconds.
/// This function is only available in Windows.
bool SleepWithWriterForTime(ReaderWriterLock& lock, vint ms);
#endif
/// Wake one thread that pending on this condition variable.
void WakeOnePending();
/// Wake all thread that pending on this condition variable.
void WakeAllPendings();
};
/***********************************************************************
User Mode Objects
***********************************************************************/
///
/// Spin lock. It is similar to mutex, but it does not occupy resource in the system.
/// The macro "SPIN_LOCK" is recommended instead of calling [M:vl.SpinLock.Enter] and [M:vl.SpinLock.Leave] like this:
///
///
class SpinLock : public Object
{
protected:
atomic_vint token = 0;
public:
NOT_COPYABLE(SpinLock);
/// Create a spin lock.
SpinLock() = default;
~SpinLock() = default;
/// Try enter a spin lock. This function will return immediately.
/// Returns true if the current thread owned the spin lock.
bool TryEnter();
/// Enter a spin lock.
void Enter();
/// Leave a spin lock.
void Leave();
public:
class Scope : public Object
{
private:
SpinLock* spinLock;
public:
NOT_COPYABLE(Scope);
Scope(SpinLock& _spinLock);
~Scope();
};
};
#define SPIN_LOCK(LOCK) SCOPE_VARIABLE(const SpinLock::Scope&, scope, LOCK)
#define CS_LOCK(LOCK) SCOPE_VARIABLE(const CriticalSection::Scope&, scope, LOCK)
#define READER_LOCK(LOCK) SCOPE_VARIABLE(const ReaderWriterLock::ReaderScope&, scope, LOCK)
#define WRITER_LOCK(LOCK) SCOPE_VARIABLE(const ReaderWriterLock::WriterScope&, scope, LOCK)
/***********************************************************************
Thread Local Storage
***********************************************************************/
/// Thread local storage operations.
///
/// This class is designed to define global variables.
/// Dynamically allocation will result in undefined behavior.
///
class ThreadLocalStorage : public Object
{
typedef void(*Destructor)(void*);
protected:
vuint64_t key;
Destructor destructor;
volatile bool disposed = false;
static void PushStorage(ThreadLocalStorage* storage);
public:
NOT_COPYABLE(ThreadLocalStorage);
ThreadLocalStorage(Destructor _destructor);
~ThreadLocalStorage();
void* Get();
void Set(void* data);
void Clear();
void Dispose();
/// Fix all storage creation.
static void FixStorages();
/// Clear all storages for the current thread. For threads that are created using [T:vl.Thread], this function will be automatically called when before the thread exit.
static void ClearStorages();
/// Clear all storages for the current thread (should be the main thread) and clear all records. This function can only be called by the main thread when all other threads are exited. It will reduce noices for detecting memory leaks.
static void DisposeStorages();
};
/// Thread local variable. Different threads can store different values to and obtain differnt values from a thread local variable.
/// Type of the storage.
///
/// This class is designed to define global variables.
/// Dynamically allocation will result in undefined behavior.
///
template
class ThreadVariable : public Object
{
protected:
ThreadLocalStorage storage;
static void Destructor(void* data)
{
if (data)
{
delete (T*)data;
}
}
public:
NOT_COPYABLE(ThreadVariable);
/// Create a thread local variable.
ThreadVariable()
:storage(&Destructor)
{
}
~ThreadVariable()
{
}
/// Test if the storage has data.
/// Returns true if the storage has data.
bool HasData()
{
return storage.Get() != nullptr;
}
/// Remove the data from this storage.
void Clear()
{
storage.Clear();
}
/// Get the stored data.
/// The stored ata.
T& Get()
{
return *(T*)storage.Get();
}
/// Set data to this storage.
/// The data to set.
void Set(const T& value)
{
storage.Clear();
storage.Set(new T(value));
}
};
template
class ThreadVariable : public Object
{
protected:
ThreadLocalStorage storage;
public:
NOT_COPYABLE(ThreadVariable);
ThreadVariable()
:storage(nullptr)
{
}
~ThreadVariable()
{
}
bool HasData()
{
return storage.Get() != nullptr;
}
void Clear()
{
storage.Set(nullptr);
}
T* Get()
{
return (T*)storage.Get();
}
void Set(T* value)
{
storage.Set((void*)value);
}
};
/***********************************************************************
RepeatingTaskExecutor
***********************************************************************/
///
/// Queued task executor. It is different from a thread because:
///
/// - Task execution is single threaded.
/// - If you queue a task, it will override all unexecuted queued tasks.
///
///
/// The type of the argument to run a task.
template
class RepeatingTaskExecutor : public Object
{
private:
SpinLock inputLock;
T inputData;
volatile bool inputDataAvailable;
SpinLock executingEvent;
volatile bool executing;
void ExecutingProcInternal()
{
while(true)
{
bool currentInputDataAvailable;
T currentInputData;
SPIN_LOCK(inputLock)
{
currentInputData=inputData;
inputData=T();
currentInputDataAvailable=inputDataAvailable;
inputDataAvailable=false;
if(!currentInputDataAvailable)
{
executing=false;
goto FINISH_EXECUTING;
}
}
Execute(currentInputData);
}
FINISH_EXECUTING:
executingEvent.Leave();
}
static void ExecutingProc(void* argument)
{
((RepeatingTaskExecutor*)argument)->ExecutingProcInternal();
}
protected:
/// This function is called when it is ready to execute a task. Task execution is single threaded. All task code should be put inside the function.
/// The argument to run a task.
virtual void Execute(const T& input)=0;
public:
/// Create a task executor.
RepeatingTaskExecutor()
:inputDataAvailable(false)
,executing(false)
{
}
~RepeatingTaskExecutor()
{
EnsureTaskFinished();
}
/// Wait for all tasks to finish.
void EnsureTaskFinished()
{
executingEvent.Enter();
executingEvent.Leave();
}
/// Queue a task.
/// The argument to run a task.
///
///
/// When there is a running task, queuing a new task will cancel all unexecuted queued tasks.
/// When there is no running task, queuing a task will execute this task immediately.
///
///
void SubmitTask(const T& input)
{
SPIN_LOCK(inputLock)
{
inputData=input;
inputDataAvailable=true;
}
if(!executing)
{
executing=true;
executingEvent.Enter();
ThreadPoolLite::Queue(&ExecutingProc, this);
}
}
};
}
#endif
/***********************************************************************
.\INTERPROCESS\CHANNEL.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Interfaces:
IChannel
***********************************************************************/
#ifndef VCZH_INTERPROCESS_CHANNEL
#define VCZH_INTERPROCESS_CHANNEL
namespace vl::inter_process
{
constexpr const wchar_t* ErrorChannel = L"!Error";
constexpr const wchar_t* SystemChannel = L"!System";
/***********************************************************************
IGuiRemoteProtocolChannel
***********************************************************************/
///
/// Callbacks for channel events.
///
/// The type of the package.
template
class IChannelReader : public virtual Interface
{
public:
///
/// Called when the channel receives a message.
///
/// The sender client id.
/// The message.
virtual void OnRead(vint senderClientId, const TPackage& package) = 0;
};
///
/// Represents a channel.
/// One server to client connection can host multiple channels, but each channel might only cover part of clients.
/// Channels are distinguished by their names.
/// Channel name cannot contain "!", channel name starting with "!" is reserved for system channels.
/// There will be no system channel representations, the channel name is used as a symbol between the server and clients for system events.
///
/// The type of the package.
template
class IChannel : public virtual Interface
{
public:
///
/// Get the channel name.
///
/// The name of the channel.
virtual const WString& GetChannelName() = 0;
///
/// Get the installed reader.
///
/// The installed reader.
virtual IChannelReader* GetReader() = 0;
///
/// Install a reader.
/// If multiple text messages are received before installing the reader, all messages will be pushed to the reader right away.
/// This function can only be called once to install a reader, and no uninstallation is supported.
///
/// The reader to install.
virtual void Initialize(IChannelReader* receiver) = 0;
///
/// Queue a message to send to a client using the same channel.
/// If the remote client doesn't have this channel, the message will be discarded.
///
/// The sender client id.
/// The receiver client id.
/// The message to send.
virtual void SendToClient(vint senderClientId, vint receiverClientId, const TPackage& package) = 0;
///
/// Queue a message to broadcast to all other clients using the same channel.
/// If the remote client doesn't have this channel, the message will be discarded.
///
/// The sender client id.
/// The message to broadcast.
virtual void BroadcastFromClient(vint senderClientId, const TPackage& package) = 0;
///
/// Send all queued messages.
///
/// Indicates whether the client is disconnected.
virtual void BatchWrite(bool& disconnected) = 0;
};
/***********************************************************************
IChannelClient
***********************************************************************/
enum class ClientStatus
{
Ready, // Created, ready to call WaitForServer.
WaitingForServer, // WaitForServer is called, blocked.
Connected, // Connection established.
Disconnected, // Connection lost.
};
enum class WaitForClientResult
{
Accept,
Reject,
};
///
/// Represents a client.
///
/// The type of the package.
template
class IChannelClient : public virtual Interface
{
public:
using ChannelMap = collections::Dictionary*>;
using ChannelNameList = typename ChannelMap::KeyContainer;
///
/// Called when the connection is established.
/// This function will be implemented by the user, the default implementation will be empty.
///
/// The client id.
virtual void OnConnected(vint clientId) = 0;
///
/// Called when the connection is disconnected.
/// This function will be implemented by the user, the default implementation will be empty.
///
virtual void OnDisconnected() = 0;
///
/// Called when a fetal error occurs.
/// When any fetal error is broadcasted from server side, all clients is supposed to receive such error if possible, and the server will shut down.
/// This function will be implemented by the user, the default implementation will be empty.
///
/// The error message.
virtual void OnReadError(const WString& errorMessage) = 0;
///
/// Called when a local error occurs.
/// This function will be implemented by the user, the default implementation will be empty.
///
/// The error message.
/// Indicates whether the error is not recoverable. The client will automatically stop after a fatal error.
virtual void OnLocalError(const WString& errorMessage, bool fatal) = 0;
///
/// Called when available connection names are required.
/// This function will be implemented by the user, the default implementation will be empty.
///
/// All connection names.
virtual const ChannelNameList& OnGetChannelNames() = 0;
///
/// Get all channels.
/// The returned map should be empty before the connection is established.
/// Channel objects will be created by the client implementation.
///
/// All connections.
virtual const ChannelMap& GetChannels() = 0;
///
/// Get the client id.
/// It should return -1 before the connection is established.
/// The client id will be assigned by the server.
///
/// The client id.
virtual vint GetClientId() = 0;
///
/// Block until the connection to the server is established.
/// Calling it more than once, after disconnecting or on a local client returns immediately.
///
virtual void WaitForServer() = 0;
///
/// Returns the status of the client.
///
/// The status of the client.
virtual ClientStatus GetStatus() = 0;
///
/// Raise a fatal error.
///
/// The fatal error.
virtual void BroadcastError(const WString& errorMessage) = 0;
};
/***********************************************************************
IChannelServer
***********************************************************************/
///
/// Represents a server.
///
/// The type of the package.
template
class IChannelServer : public virtual Interface
{
public:
using ClientChannelMap = collections::Group;
using ClientIdList = typename ClientChannelMap::KeyContainer;
///
/// Called when any client connects to the server.
/// The server begins listening to client connections after is called.
/// No callback happens before or after is called.
/// This function will be implemented by the user, the default implementation will return true.
///
/// The client id.
/// The available channels.
/// Returns "Reject" to disconnect the client immediatelly.
virtual WaitForClientResult OnClientConnected(vint clientId, const IChannelClient::ChannelNameList& availableChannels) = 0;
///
/// Start the server.
///
virtual void Start() = 0;
///
/// Called when any client disconnects from the server.
/// This function will be implemented by the user, the default implementation will be empty.
///
/// The client id.
virtual void OnClientDisconnected(vint clientId) = 0;
///
/// Connect a local client to the server.
/// Connections between such client to the server will be local and established immediately, no network transmission is involved.
///
/// The local client.
/// Returns the assigned client id, or -1 if the connection is already established, no matter local or remote.
virtual vint ConnectLocalClient(Ptr> localClient) = 0;
///
/// Test if a client id is local.
///
/// The client id.
/// Returns true if the client id is local, false otherwise.
virtual bool IsLocalClient(vint clientId) = 0;
///
/// Disconnect a client.
///
/// The client id.
/// Returns true if the client is successfully disconnected, false otherwise.
virtual bool DisconnectClient(vint clientId) = 0;
///
/// Get all client ids.
///
/// All client ids.
virtual const ClientIdList& GetClientIds() = 0;
///
/// Get all client channels.
/// Channel objects will be created by the server implementation.
///
/// Returns a map from client id to available channels.
virtual const ClientChannelMap& GetClientChannels() = 0;
///
/// Raise a fatal error.
///
/// The fatal error.
virtual void BroadcastError(const WString& errorMessage) = 0;
///
/// Stop the server.
///
virtual void Stop() = 0;
///
/// Test if the server has stopped.
/// A stopped server could be caused by either calling or the underlying mechanism failing.
///
/// Returns true if the server has stopped, false otherwise.
virtual bool IsStopped() = 0;
};
}
#endif
/***********************************************************************
.\INTERPROCESS\CHANNELSERIALIZER.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Interfaces:
IChannel
***********************************************************************/
#ifndef VCZH_INTERPROCESS_CHANNELSERIALIZER
#define VCZH_INTERPROCESS_CHANNELSERIALIZER
namespace vl::inter_process
{
/***********************************************************************
Serialization, it requires a serialization contract that defines as below
struct
{
using SourceType = ...;
using DestType = ...;
using ContextType = ... (use std::nullptr_t if no context is needed);
static void Serialize(const ContextType&, const SourceType& source, DestType& dest)
{
// Convert from source to dest with the context.
}
static void Deserialize(const ContextType&, const DestType& dest, SourceType& source)
{
// Convert from dest to source with the context.
}
};
***********************************************************************/
template
class ChannelTransformerBase
: public Object
, public virtual IChannel
, protected virtual IChannelReader
{
protected:
IChannel* channel = nullptr;
IChannelReader* reader = nullptr;
public:
ChannelTransformerBase(IChannel* _channel)
: channel(_channel)
{
}
const WString& GetChannelName() override
{
return channel->GetChannelName();
}
IChannelReader* GetReader() override
{
return reader;
}
void Initialize(IChannelReader* _reader) override
{
reader = _reader;
channel->Initialize(this);
}
void BatchWrite(bool& disconnected) override
{
channel->BatchWrite(disconnected);
}
};
template
class ChannelSerializer
: public ChannelTransformerBase
{
protected:
typename TSerialization::ContextType context;
void OnRead(vint senderClientId, const typename TSerialization::DestType& package) override
{
typename TSerialization::SourceType deserialized;
TSerialization::Deserialize(context, package, deserialized);
this->reader->OnRead(senderClientId, deserialized);
}
public:
ChannelSerializer(IChannel* _channel, const typename TSerialization::ContextType& _context = {})
: ChannelTransformerBase(_channel)
, context(_context)
{
}
void SendToClient(vint senderClientId, vint receiverClientId, const typename TSerialization::SourceType& package) override
{
typename TSerialization::DestType serialized;
TSerialization::Serialize(context, package, serialized);
this->channel->SendToClient(senderClientId, receiverClientId, serialized);
}
void BroadcastFromClient(vint senderClientId, const typename TSerialization::SourceType& package) override
{
typename TSerialization::DestType serialized;
TSerialization::Serialize(context, package, serialized);
this->channel->BroadcastFromClient(senderClientId, serialized);
}
};
/***********************************************************************
String Transformation
***********************************************************************/
template
struct UtfStringSerializer
{
using SourceType = ObjectString;
using DestType = ObjectString;
using ContextType = std::nullptr_t;
static void Serialize(const ContextType&, const SourceType& source, DestType& dest)
{
ConvertUtfString(source, dest);
}
static void Deserialize(const ContextType&, const DestType& dest, SourceType& source)
{
ConvertUtfString(dest, source);
}
};
template
using UtfStringChannelSerializer = ChannelSerializer>;
}
#endif
/***********************************************************************
.\INTERPROCESS\TEXTNETWORKPROTOCOL.H
***********************************************************************/
/***********************************************************************
Vczh Library++ 3.0
Developer: Zihan Chen(vczh)
Interfaces:
INetworkProtocol
***********************************************************************/
#ifndef VCZH_INTERPROCESS_TEXTNETWORKPROTOCOL
#define VCZH_INTERPROCESS_TEXTNETWORKPROTOCOL
namespace vl::inter_process
{
struct NetworkPackage
{
Nullable clientId;
WString channelName;
WString messageBody;
static inline NetworkPackage Create(Nullable _clientId, const WString& _channelName, const WString& _messageBody)
{
NetworkPackage package;
package.clientId = std::move(_clientId);
package.channelName = _channelName;
package.messageBody = _messageBody;
return package;
}
static inline WString ToString(const NetworkPackage& package)
{
return (package.clientId ? itow(package.clientId.Value()) : WString::Empty)
+ L";" + package.channelName
+ L";" + package.messageBody
;
}
static inline void Parse(const WString& str, NetworkPackage& package)
{
#define ERROR_MESSAGE_PREFIX L"vl::inter_process::NetworkPackage::Parse(const WString&, NetworkPackage&)#"
const wchar_t* reading = str.Buffer();
const wchar_t* afterClientId = wcschr(reading, L';');
CHECK_ERROR(afterClientId != nullptr, ERROR_MESSAGE_PREFIX L"Invalid package format.");
if (afterClientId == reading)
{
package.clientId.Reset();
}
else
{
package.clientId = wtoi(str.Left((vint)(afterClientId - reading)));
}
const wchar_t* afterChannelName = wcschr(afterClientId + 1, L';');
CHECK_ERROR(afterChannelName != nullptr, ERROR_MESSAGE_PREFIX L"Invalid package format.");
package.channelName = str.Sub((vint)(afterClientId - reading + 1), (vint)(afterChannelName - afterClientId - 1));
package.messageBody = str.Right(str.Length() - (vint)(afterChannelName - reading + 1));
#undef ERROR_MESSAGE_PREFIX
}
};
/***********************************************************************
INetworkProtocolServer
***********************************************************************/
class INetworkProtocolConnection;
///
/// Callbacks for network protocol events.
/// Functions could be run in any thread, implementation should be thread-safe.
///
class INetworkProtocolCallback : public virtual Interface
{
public:
///
/// Called when a text message is received from the other side of the connection.
///
/// The text message.
virtual void OnReadString(const WString& str) = 0;
///
/// Called when an error message is received from the other side of the connection.
///
/// The error message.
virtual void OnReadError(const WString& error) = 0;
///
/// Called when a local transport error occurs.
///
/// The error message.
/// Indicates whether the connection should be disconnected after this callback.
virtual void OnLocalError(const WString& error, bool fatal) = 0;
///
/// Called when the connection becomes available.
/// This function might not be called if is called after the connection is already established.
///
virtual void OnConnected() = 0;
///
/// Called when the connection is lost.
///
virtual void OnDisconnected() = 0;
///
/// Called when the callback is installed to a connection.
///
///
virtual void OnInstalled(INetworkProtocolConnection* connection) = 0;
};
///
/// Represents a network connection, exchanging messages between the server and one client.
/// After the connection is lost, it won't reconnect, if the server supports reconnection, a new connection object will be created.
/// One connection object can be obtained from the client.
/// Multiple connection objects can be obtained from the server.
///
class INetworkProtocolConnection : public virtual Interface
{
public:
///
/// Install the callback to the connection.
/// If multiple text messages are received before installing the callback, all text messages will be pushed to the callback right away.
/// This function can only be called once to install a callback, and no uninstallation is supported.
///
/// The callback object. It should not be null.
virtual void InstallCallback(INetworkProtocolCallback* callback) = 0;
///
/// Start receiving messages asynchronously until the connection is lost.
/// Some implementation may start receiving messages immediately after the connection is established.
/// So there is no guarantee that messages should not arrive before calling this function.
///
virtual void BeginReadingLoopUnsafe() = 0;
///
/// Send a text message to the other side of the connection.
///
/// The text message to send.
virtual void SendString(const WString& str) = 0;
///
/// Stop the connection.
///
virtual void Stop() = 0;
};
///
/// Represents a client.
///
class INetworkProtocolClient : public virtual Interface
{
public:
///
/// Obtain the connection to the server.
/// An valid object will always be returned, but before finishing, using the connection is undefined behavior.
///
/// The connection to the server.
virtual INetworkProtocolConnection* GetConnection() = 0;
///
/// Block until the connection to the server is established.
///
virtual void WaitForServer() = 0;
///
/// Returns the status of the client.
///
/// The status of the client.
virtual ClientStatus GetStatus() = 0;
};
///
/// Represents a server.
///
class INetworkProtocolServer : public virtual Interface
{
public:
///
/// Called when a client connects to the server.
/// The server begins listening to client connections after is called.
/// No callback happens before or after is called.
///
/// A connection object representing the client.
/// Returns "Reject" to disconnect the client immediatelly.
virtual WaitForClientResult OnClientConnected(INetworkProtocolConnection* connection) = 0;
///
/// Start the server.
///
virtual void Start() = 0;
///
/// Stop the server.
///
virtual void Stop() = 0;
///
/// Test if the server has stopped.
/// A stopped server could be caused by either calling or the underlying mechanism failing.
///
/// Returns true if the server has stopped, false otherwise.
virtual bool IsStopped() = 0;
};
/***********************************************************************
Hooking IChannelServer/IChannelClient to INetworkProtocolServer/INetworkProtocolClient
The serialization contract is the same to the one described in ChannelSerialization.h
SourceType will be List
DestType will be WString
NetworkPackage will be used as text message parsing and formatting for INetworkProtocolConnection.
BatchWrite belongs to IChannel, meaning each channel sends its own batch messages in one NetworkPackage.
channelName will be either a system channel or a user defined channel.
messageBody represents a list of TPackage.
When sending from client to server, clientId means the target client.
Empty means broadcasting.
When sending from server to client, clientId means the source client.
Channel messages delivered by the server always carry a source client id.
When a client establishes a connection to the server, channel names will be sent to the server:
clientId will be empty, it does not mean broadcasting.
channelName will be empty.
messageBody will be all available channel names joined by "!", as "!" cannot be part of the channel name anyway.
After the server receives the first message from a client, an client id will be sent to the client:
clientId is the assigned client id, starting from 1.
channelName will be empty.
messageBody will be empty.
Later
***********************************************************************/
/***********************************************************************
NetworkProtocolChannel
***********************************************************************/
template
class NetworkProtocolChannel : public Object, public virtual IChannel
{
static_assert(std::is_same_v>);
static_assert(std::is_same_v);
protected:
using PackageList = typename TSerialization::SourceType;
using ChannelMap = collections::Dictionary*>;
using ChannelNameList = typename ChannelMap::KeyContainer;
struct UnreadPackage
{
vint senderClientId = -1;
TPackage package;
};
struct QueuedPackage
{
vint senderClientId = -1;
Nullable receiverClientId;
TPackage package;
};
WString channelName;
// covers reader and unreadPackages
SpinLock lockUnreadPackages;
IChannelReader* reader = nullptr;
collections::List unreadPackages;
// covers queuedPackages
SpinLock lockQueuedPackages;
collections::List queuedPackages;
virtual void ValidatePackage(vint senderClientId, Nullable receiverClientId) = 0;
virtual bool WriteBatch(vint senderClientId, Nullable receiverClientId, const PackageList& batch) = 0;
public:
NetworkProtocolChannel(const WString& _channelName)
: channelName(_channelName)
{
}
static void ValidateChannelName(const WString& channelName)
{
CHECK_ERROR(channelName.Length() > 0, L"Channel name should not be empty.");
CHECK_ERROR(wcschr(channelName.Buffer(), L'!') == nullptr, L"Channel name should not contain !.");
}
static WString JoinChannelNames(const ChannelNameList& channelNames)
{
WString joinedNames;
for (auto&& channelName : channelNames)
{
if (joinedNames.Length() > 0)
{
joinedNames += L"!";
}
joinedNames += channelName;
}
return joinedNames;
}
static void SplitChannelNames(const WString& joinedNames, ChannelMap& availableChannels)
{
const wchar_t* reading = joinedNames.Buffer();
while (true)
{
auto delimiter = wcschr(reading, L'!');
auto channelName =
delimiter
? WString::CopyFrom(reading, (vint)(delimiter - reading))
: WString::CopyFrom(reading, (vint)wcslen(reading));
if (channelName.Length() > 0)
{
ValidateChannelName(channelName);
availableChannels.Add(channelName, nullptr);
}
if (!delimiter)
{
break;
}
reading = delimiter + 1;
}
}
const WString& GetChannelName() override
{
return channelName;
}
IChannelReader* GetReader() override
{
return reader;
}
void Initialize(IChannelReader* _reader) override
{
CHECK_ERROR(_reader, L"NetworkProtocolChannel::Initialize needs a valid reader.");
SPIN_LOCK(lockUnreadPackages)
{
CHECK_ERROR(!reader, L"NetworkProtocolChannel::Initialize cannot be called more than once.");
reader = _reader;
for (auto&& package : unreadPackages)
{
reader->OnRead(package.senderClientId, package.package);
}
unreadPackages.Clear();
}
}
void SendToClient(vint senderClientId, vint receiverClientId, const TPackage& package) override
{
ValidatePackage(senderClientId, receiverClientId);
QueuePackage(senderClientId, receiverClientId, package);
}
void BroadcastFromClient(vint senderClientId, const TPackage& package) override
{
ValidatePackage(senderClientId, {});
QueuePackage(senderClientId, {}, package);
}
void BatchWrite(bool& disconnected) override
{
collections::List packages;
SPIN_LOCK(lockQueuedPackages)
{
packages = std::move(queuedPackages);
}
while (packages.Count() > 0)
{
PackageList batch;
auto senderClientId = packages[0].senderClientId;
auto receiverClientId = packages[0].receiverClientId;
for (vint i = 0; i < packages.Count();)
{
auto&& package = packages[i];
if (package.senderClientId == senderClientId && package.receiverClientId == receiverClientId)
{
batch.Add(package.package);
packages.RemoveAt(i);
}
else
{
i++;
}
}
if (WriteBatch(senderClientId, receiverClientId, batch))
{
disconnected = true;
return;
}
}
}
void ReadBatch(vint senderClientId, const PackageList& batch)
{
SPIN_LOCK(lockUnreadPackages)
{
if (!reader)
{
for (auto&& package : batch)
{
UnreadPackage unreadPackage;
unreadPackage.senderClientId = senderClientId;
unreadPackage.package = package;
unreadPackages.Add(unreadPackage);
}
return;
}
}
for (auto&& package : batch)
{
reader->OnRead(senderClientId, package);
}
}
protected:
void QueuePackage(vint senderClientId, Nullable receiverClientId, const TPackage& package)
{
SPIN_LOCK(lockQueuedPackages)
{
QueuedPackage queuedPackage;
queuedPackage.senderClientId = senderClientId;
queuedPackage.receiverClientId = receiverClientId;
queuedPackage.package = package;
queuedPackages.Add(queuedPackage);
}
}
};
/***********************************************************************
NetworkProtocolChannelClientBase
***********************************************************************/
template
class NetworkProtocolChannelClientBase : public Object, public virtual IChannelClient
{
protected:
using BaseChannel = NetworkProtocolChannel;
using PackageList = typename TSerialization::SourceType;
using ChannelMap = typename IChannelClient::ChannelMap;
using ChannelNameList = typename IChannelClient::ChannelNameList;
private:
class Channel : public NetworkProtocolChannel
{
using Base = NetworkProtocolChannel;
private:
NetworkProtocolChannelClientBase* client = nullptr;
void ValidatePackage(vint senderClientId, Nullable receiverClientId) override
{
auto currentClientId = client->GetClientId();
CHECK_ERROR(currentClientId != -1, L"NetworkProtocolChannelClient::Channel needs to be connected before sending.");
CHECK_ERROR(senderClientId == currentClientId, L"NetworkProtocolChannelClient::Channel needs senderClientId to match the current client id.");
if (receiverClientId)
{
CHECK_ERROR(receiverClientId.Value() > 0, L"NetworkProtocolChannelClient::Channel needs a valid receiverClientId.");
}
}
bool WriteBatch(vint, Nullable receiverClientId, const PackageList& batch) override
{
return client->SendBatch(receiverClientId, this->channelName, batch);
}
public:
Channel(NetworkProtocolChannelClientBase* _client, const WString& _channelName)
: Base(_channelName)
, client(_client)
{
}
};
protected:
typename TSerialization::ContextType context;
// covers status, connectedNotified and clientId
SpinLock lockStatus;
ClientStatus status = ClientStatus::Ready;
bool connectedNotified = false;
vint clientId = -1;
private:
ChannelMap channels;
collections::Dictionary> ownedChannels;
protected:
Channel* FindChannel(const WString& channelName)
{
vint index = ownedChannels.Keys().IndexOf(channelName);
return index == -1 ? nullptr : ownedChannels.Values()[index].Obj();
}
void SetStatus(ClientStatus newStatus)
{
SPIN_LOCK(lockStatus)
{
status = newStatus;
}
}
void SetConnected(vint assignedClientId)
{
SPIN_LOCK(lockStatus)
{
clientId = assignedClientId;
status = ClientStatus::Connected;
}
}
bool TrySetConnected(vint assignedClientId)
{
bool connected = false;
SPIN_LOCK(lockStatus)
{
if (status == ClientStatus::Ready || status == ClientStatus::WaitingForServer)
{
clientId = assignedClientId;
status = ClientStatus::Connected;
connected = true;
}
}
return connected;
}
virtual bool SendBatch(Nullable receiverClientId, const WString& channelName, const PackageList& batch) = 0;
void ReceiveBatch(const WString& channelName, vint senderClientId, const WString& messageBody)
{
auto channel = FindChannel(channelName);
if (channel)
{
PackageList batch;
TSerialization::Deserialize(context, messageBody, batch);
channel->ReadBatch(senderClientId, batch);
}
}
virtual void NotifyDisconnected()
{
bool shouldNotify = false;
SPIN_LOCK(lockStatus)
{
if (status != ClientStatus::Disconnected)
{
status = ClientStatus::Disconnected;
shouldNotify = true;
}
}
if (shouldNotify)
{
OnDisconnected();
}
}
void NotifyConnected()
{
bool shouldNotify = false;
vint connectedClientId = -1;
SPIN_LOCK(lockStatus)
{
if (status == ClientStatus::Connected && !connectedNotified)
{
connectedNotified = true;
connectedClientId = clientId;
shouldNotify = true;
}
}
if (shouldNotify)
{
OnConnected(connectedClientId);
}
}
public:
void OnConnected(vint clientId) override
{
// default implementation does nothing
}
void OnDisconnected() override
{
// default implementation does nothing
}
void OnReadError(const WString& errorMessage) override
{
// default implementation does nothing
}
void OnLocalError(const WString& errorMessage, bool fatal) override
{
// default implementation does nothing
}
protected:
NetworkProtocolChannelClientBase(const typename TSerialization::ContextType& _context = {})
: context(_context)
{
}
private:
IChannel* CreateChannel(const WString& channelName)
{
BaseChannel::ValidateChannelName(channelName);
vint index = channels.Keys().IndexOf(channelName);
if (index != -1)
{
return channels.Values()[index];
}
auto channel = Ptr(new Channel(this, channelName));
ownedChannels.Add(channelName, channel);
channels.Add(channelName, channel.Obj());
return channel.Obj();
}
protected:
void EnsureChannels(const ChannelNameList& channelNames)
{
for (auto&& channelName : channelNames)
{
CreateChannel(channelName);
}
}
public:
const ChannelNameList& OnGetChannelNames() override
{
return channels.Keys();
}
const ChannelMap& GetChannels() override
{
EnsureChannels(OnGetChannelNames());
return channels;
}
vint GetClientId() override
{
vint result = -1;
SPIN_LOCK(lockStatus)
{
result = clientId;
}
return result;
}
ClientStatus GetStatus() override
{
ClientStatus result = ClientStatus::Disconnected;
SPIN_LOCK(lockStatus)
{
result = status;
}
return result;
}
};
/***********************************************************************
NetworkProtocolChannelClient
***********************************************************************/
template
class NetworkProtocolChannelClient : public NetworkProtocolChannelClientBase
{
protected:
using Base = NetworkProtocolChannelClientBase;
using BaseChannel = typename Base::BaseChannel;
using PackageList = typename TSerialization::SourceType;
class Callback : public Object, public virtual INetworkProtocolCallback
{
private:
NetworkProtocolChannelClient* client = nullptr;
public:
Callback(NetworkProtocolChannelClient* _client)
: client(_client)
{
}
void OnReadString(const WString& str) override
{
client->OnReadString(str);
}
void OnReadError(const WString& error) override
{
client->OnReadError(error);
client->NotifyDisconnected();
}
void OnLocalError(const WString& error, bool fatal) override
{
client->OnLocalError(error, fatal);
if (fatal)
{
client->NotifyDisconnected();
}
}
void OnConnected() override
{
}
void OnDisconnected() override
{
client->NotifyDisconnected();
}
void OnInstalled(INetworkProtocolConnection*) override
{
}
};
private:
EventObject eventWaitForServer;
Ptr callback;
Ptr npClient;
SpinLock lockQueuedPackagesBeforeConnected;
collections::List queuedPackagesBeforeConnected;
protected:
bool SendBatch(Nullable receiverClientId, const WString& channelName, const PackageList& batch) override
{
if (this->GetStatus() != ClientStatus::Connected)
{
return true;
}
CHECK_ERROR(npClient, L"NetworkProtocolChannelClient::SendBatch needs an established network connection.");
WString messageBody;
TSerialization::Serialize(this->context, batch, messageBody);
npClient->GetConnection()->SendString(NetworkPackage::ToString(NetworkPackage::Create(std::move(receiverClientId), channelName, messageBody)));
return false;
}
private:
void OnReadChannelPackage(const NetworkPackage& package)
{
if (this->FindChannel(package.channelName))
{
CHECK_ERROR(package.clientId, L"NetworkProtocolChannelClient received a channel message without senderClientId.");
auto senderClientId = package.clientId.Value();
CHECK_ERROR(senderClientId > 0, L"NetworkProtocolChannelClient received an invalid senderClientId.");
this->ReceiveBatch(package.channelName, senderClientId, package.messageBody);
}
}
void OnReadString(const WString& str)
{
NetworkPackage package;
NetworkPackage::Parse(str, package);
if (package.channelName == ErrorChannel)
{
this->OnReadError(package.messageBody);
NotifyDisconnected();
return;
}
else if (package.channelName == WString::Empty)
{
CHECK_ERROR(package.clientId, L"NetworkProtocolChannelClient received an invalid connection response.");
CHECK_ERROR(package.clientId.Value() > 0, L"NetworkProtocolChannelClient received an invalid client id.");
this->SetConnected(package.clientId.Value());
this->NotifyConnected();
eventWaitForServer.Signal();
collections::List packages;
SPIN_LOCK(lockQueuedPackagesBeforeConnected)
{
packages = std::move(queuedPackagesBeforeConnected);
}
for (auto&& queuedPackage : packages)
{
OnReadChannelPackage(queuedPackage);
}
return;
}
if (this->GetStatus() != ClientStatus::Connected)
{
SPIN_LOCK(lockQueuedPackagesBeforeConnected)
{
if (this->GetStatus() != ClientStatus::Connected)
{
queuedPackagesBeforeConnected.Add(std::move(package));
return;
}
}
}
OnReadChannelPackage(package);
}
protected:
void NotifyDisconnected() override
{
eventWaitForServer.Signal();
Base::NotifyDisconnected();
}
protected:
NetworkProtocolChannelClient(const typename TSerialization::ContextType& _context = {})
: Base(_context)
{
CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"NetworkProtocolChannelClient initialization failed on eventWaitForServer.");
}
public:
NetworkProtocolChannelClient(
Ptr _npClient,
const typename TSerialization::ContextType& _context = {}
)
: Base(_context)
{
CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"NetworkProtocolChannelClient initialization failed on eventWaitForServer.");
CHECK_ERROR(_npClient, L"NetworkProtocolChannelClient needs a valid INetworkProtocolClient.");
callback = Ptr(new Callback(this));
npClient = _npClient;
npClient->GetConnection()->InstallCallback(callback.Obj());
}
~NetworkProtocolChannelClient()
{
if (npClient)
{
npClient->GetConnection()->Stop();
}
}
void WaitForServer() override
{
auto currentStatus = this->GetStatus();
if (currentStatus == ClientStatus::Connected || currentStatus == ClientStatus::Disconnected)
{
this->NotifyConnected();
return;
}
if (currentStatus == ClientStatus::WaitingForServer)
{
eventWaitForServer.Wait();
this->NotifyConnected();
return;
}
CHECK_ERROR(currentStatus == ClientStatus::Ready, L"NetworkProtocolChannelClient::WaitForServer found an unexpected client status.");
this->SetStatus(ClientStatus::WaitingForServer);
npClient->WaitForServer();
if (npClient->GetStatus() != ClientStatus::Connected)
{
NotifyDisconnected();
return;
}
auto&& channelNames = this->OnGetChannelNames();
this->EnsureChannels(channelNames);
npClient->GetConnection()->SendString(NetworkPackage::ToString(NetworkPackage::Create({}, WString::Empty, BaseChannel::JoinChannelNames(channelNames))));
npClient->GetConnection()->BeginReadingLoopUnsafe();
eventWaitForServer.Wait();
this->NotifyConnected();
}
void BroadcastError(const WString& errorMessage) override
{
CHECK_ERROR(npClient, L"NetworkProtocolChannelClient::BroadcastError needs an established connection.");
npClient->GetConnection()->SendString(NetworkPackage::ToString(NetworkPackage::Create({}, WString::Unmanaged(ErrorChannel), errorMessage)));
NotifyDisconnected();
}
};
/***********************************************************************
NetworkProtocolLocalChannelClient
***********************************************************************/
template
class NetworkProtocolChannelServer;
template
class NetworkProtocolLocalChannelClient : public NetworkProtocolChannelClientBase
{
friend class NetworkProtocolChannelServer;
private:
using Base = NetworkProtocolChannelClientBase;
using PackageList = typename TSerialization::SourceType;
NetworkProtocolChannelServer* localServer = nullptr;
bool ConnectLocalServer(NetworkProtocolChannelServer* server, vint assignedClientId)
{
CHECK_ERROR(server, L"NetworkProtocolLocalChannelClient::ConnectLocalServer needs a valid server.");
localServer = server;
bool connected = this->TrySetConnected(assignedClientId);
if (!connected)
{
localServer = nullptr;
}
return connected;
}
void NotifyLocalConnected()
{
this->NotifyConnected();
}
protected:
bool SendBatch(Nullable receiverClientId, const WString& channelName, const PackageList& batch) override
{
if (this->GetStatus() != ClientStatus::Connected)
{
return true;
}
if (localServer)
{
return localServer->SendFromLocalClient(receiverClientId, this->GetClientId(), channelName, batch);
}
return true;
}
void NotifyDisconnected() override
{
localServer = nullptr;
Base::NotifyDisconnected();
}
public:
NetworkProtocolLocalChannelClient(const typename TSerialization::ContextType& _context = {})
: Base(_context)
{
}
void WaitForServer() override
{
}
void BroadcastError(const WString& errorMessage) override
{
if (localServer)
{
localServer->BroadcastError(errorMessage);
return;
}
this->OnLocalError(errorMessage, true);
this->NotifyDisconnected();
}
};
/***********************************************************************
NetworkProtocolChannelServer
***********************************************************************/
template
class NetworkProtocolChannelServer : public Object, public virtual IChannelServer, public virtual INetworkProtocolServer
{
friend class NetworkProtocolLocalChannelClient;
private:
using BaseChannel = NetworkProtocolChannel;
using PackageList = typename TSerialization::SourceType;
using ChannelMap = typename IChannelClient::ChannelMap;
using ChannelNameList = typename IChannelClient::ChannelNameList;
using ClientChannelMap = typename IChannelServer::ClientChannelMap;
using ClientIdList = typename IChannelServer::ClientIdList;
using LocalChannelClient = NetworkProtocolLocalChannelClient;
class Connection : public Object, public virtual INetworkProtocolCallback
{
private:
NetworkProtocolChannelServer* server = nullptr;
public:
INetworkProtocolConnection* connection = nullptr;
vint clientId = -1;
bool accepted = false;
Connection(NetworkProtocolChannelServer* _server)
: server(_server)
{
}
void OnReadString(const WString& str) override
{
server->OnReadString(this, str);
}
void OnReadError(const WString& error) override
{
server->BroadcastError(error);
}
void OnLocalError(const WString& error, bool fatal) override
{
// Server-side transport errors are finalized by OnDisconnected.
}
void OnConnected() override
{
}
void OnDisconnected() override
{
server->OnConnectionDisconnected(this);
}
void OnInstalled(INetworkProtocolConnection* _connection) override
{
connection = _connection;
}
};
typename TSerialization::ContextType context;
// covers connections, localClients, pendingConnections, clientChannels, nextClientId, started and stopped
SpinLock lockConnections;
collections::Dictionary> connections;
collections::Dictionary> localClients;
collections::List> pendingConnections;
ClientChannelMap clientChannels;
vint nextClientId = 1;
bool started = false;
bool stopped = false;
bool ClientHasChannel(vint clientId, const WString& channelName)
{
bool result = false;
SPIN_LOCK(lockConnections)
{
result = (connections.Keys().Contains(clientId) || localClients.Keys().Contains(clientId)) && clientChannels.Contains(clientId, channelName);
}
return result;
}
Ptr RemovePendingConnection(Connection* connection)
{
Ptr pendingConnection;
for (vint i = 0; i < pendingConnections.Count(); i++)
{
if (pendingConnections[i].Obj() == connection)
{
pendingConnection = pendingConnections[i];
pendingConnections.RemoveAt(i);
break;
}
}
return pendingConnection;
}
void OnReadString(Connection* connection, const WString& str)
{
NetworkPackage package;
NetworkPackage::Parse(str, package);
if (package.channelName == ErrorChannel)
{
BroadcastError(package.messageBody);
return;
}
if (connection->clientId == -1)
{
CHECK_ERROR(!package.clientId && package.channelName == WString::Empty, L"NetworkProtocolChannelServer received an invalid connection request.");
ChannelMap availableChannels;
BaseChannel::SplitChannelNames(package.messageBody, availableChannels);
Ptr pendingConnection;
vint assignedClientId = -1;
{
SPIN_LOCK(lockConnections)
{
pendingConnection = RemovePendingConnection(connection);
CHECK_ERROR(pendingConnection, L"NetworkProtocolChannelServer failed to find a pending connection.");
assignedClientId = nextClientId++;
}
}
if (OnClientConnected(assignedClientId, availableChannels.Keys()) == WaitForClientResult::Accept)
{
bool accepted = false;
{
SPIN_LOCK(lockConnections)
{
if (!stopped)
{
connection->clientId = assignedClientId;
connection->accepted = true;
connections.Add(assignedClientId, pendingConnection);
for (auto&& channelName : availableChannels.Keys())
{
clientChannels.Add(assignedClientId, channelName);
}
accepted = true;
}
}
}
if (accepted)
{
connection->connection->SendString(NetworkPackage::ToString(NetworkPackage::Create(assignedClientId, WString::Empty, WString::Empty)));
}
else
{
connection->connection->Stop();
}
}
else
{
connection->connection->Stop();
}
return;
}
CHECK_ERROR(ClientHasChannel(connection->clientId, package.channelName), L"NetworkProtocolChannelServer received a message from a client without the specified channel.");
if (package.clientId)
{
auto receiverClientId = package.clientId.Value();
CHECK_ERROR(receiverClientId > 0 && ClientHasChannel(receiverClientId, package.channelName), L"NetworkProtocolChannelServer received a message to a client without the specified channel.");
}
PackageList batch;
TSerialization::Deserialize(context, package.messageBody, batch);
SendBatch(package.clientId, connection->clientId, connection->clientId, package.channelName, batch);
}
void OnConnectionDisconnected(Connection* connection)
{
Ptr disconnectedConnection;
vint disconnectedClientId = -1;
{
SPIN_LOCK(lockConnections)
{
if (connection->clientId == -1)
{
disconnectedConnection = RemovePendingConnection(connection);
}
else if (connections.Keys().Contains(connection->clientId))
{
disconnectedClientId = connection->clientId;
disconnectedConnection = connections[connection->clientId];
connections.Remove(connection->clientId);
clientChannels.Remove(connection->clientId);
}
}
}
if (disconnectedClientId != -1)
{
OnClientDisconnected(disconnectedClientId);
}
}
void NotifyLocalClientDisconnected(Ptr localClient)
{
localClient->NotifyDisconnected();
}
void DeliverBatchToLocalClient(Ptr localClient, vint senderClientId, const WString& channelName, const PackageList& batch)
{
auto&& channels = localClient->GetChannels();
auto index = channels.Keys().IndexOf(channelName);
CHECK_ERROR(index != -1, L"NetworkProtocolChannelServer failed to find a local channel.");
auto channel = channels.Values()[index];
if (auto networkChannel = dynamic_cast(channel))
{
networkChannel->ReadBatch(senderClientId, batch);
}
else
{
auto reader = channel->GetReader();
CHECK_ERROR(reader, L"NetworkProtocolChannelServer needs a readable local channel.");
for (auto&& package : batch)
{
reader->OnRead(senderClientId, package);
}
}
}
bool SendBatch(Nullable receiverClientId, vint senderClientId, vint excludedClientId, const WString& channelName, const PackageList& batch)
{
if (IsStopped())
{
return true;
}
WString messageBody;
TSerialization::Serialize(context, batch, messageBody);
if (receiverClientId)
{
Ptr connection;
Ptr localClient;
{
SPIN_LOCK(lockConnections)
{
if (connections.Keys().Contains(receiverClientId.Value()) && clientChannels.Contains(receiverClientId.Value(), channelName))
{
connection = connections[receiverClientId.Value()];
}
else if (localClients.Keys().Contains(receiverClientId.Value()) && clientChannels.Contains(receiverClientId.Value(), channelName))
{
localClient = localClients[receiverClientId.Value()];
}
}
}
if (connection)
{
connection->connection->SendString(NetworkPackage::ToString(NetworkPackage::Create(senderClientId, channelName, messageBody)));
}
if (localClient)
{
DeliverBatchToLocalClient(localClient, senderClientId, channelName, batch);
}
}
else
{
collections::List> targetConnections;
collections::List> targetLocalClients;
{
SPIN_LOCK(lockConnections)
{
for (auto&& connection : connections.Values())
{
if (connection->clientId != excludedClientId && clientChannels.Contains(connection->clientId, channelName))
{
targetConnections.Add(connection);
}
}
for (auto&& clientId : localClients.Keys())
{
if (clientId != excludedClientId && clientChannels.Contains(clientId, channelName))
{
targetLocalClients.Add(localClients[clientId]);
}
}
}
}
for (auto&& connection : targetConnections)
{
connection->connection->SendString(NetworkPackage::ToString(NetworkPackage::Create(senderClientId, channelName, messageBody)));
}
for (auto&& localClient : targetLocalClients)
{
DeliverBatchToLocalClient(localClient, senderClientId, channelName, batch);
}
}
return false;
}
bool SendFromLocalClient(Nullable receiverClientId, vint senderClientId, const WString& channelName, const PackageList& batch)
{
CHECK_ERROR(senderClientId > 0 && ClientHasChannel(senderClientId, channelName), L"NetworkProtocolChannelServer received a message from a local client without the specified channel.");
if (receiverClientId)
{
CHECK_ERROR(receiverClientId.Value() > 0 && ClientHasChannel(receiverClientId.Value(), channelName), L"NetworkProtocolChannelServer received a message from a local client to a client without the specified channel.");
}
return SendBatch(receiverClientId, senderClientId, senderClientId, channelName, batch);
}
public:
WaitForClientResult OnClientConnected(INetworkProtocolConnection* connection) override
{
CHECK_ERROR(connection, L"NetworkProtocolChannelServer::OnClientConnected needs a valid connection.");
auto context = Ptr(new Connection(this));
context->connection = connection;
{
SPIN_LOCK(lockConnections)
{
if (!started || stopped)
{
return WaitForClientResult::Reject;
}
pendingConnections.Add(context);
}
}
connection->InstallCallback(context.Obj());
connection->BeginReadingLoopUnsafe();
return WaitForClientResult::Accept;
}
void Start() override
{
SPIN_LOCK(lockConnections)
{
CHECK_ERROR(!stopped, L"NetworkProtocolChannelServer has stopped.");
started = true;
}
}
WaitForClientResult OnClientConnected(vint clientId, const typename IChannelClient::ChannelNameList& availableChannels) override
{
// default implementation allows all clients to connect
return WaitForClientResult::Accept;
}
void OnClientDisconnected(vint clientId) override
{
// default implementation does nothing
}
NetworkProtocolChannelServer(
const typename TSerialization::ContextType& _context = {}
)
: context(_context)
{
}
~NetworkProtocolChannelServer()
{
Stop();
}
vint ConnectLocalClient(Ptr> localClient) override
{
CHECK_ERROR(localClient, L"NetworkProtocolChannelServer::ConnectLocalClient needs a valid localClient.");
{
SPIN_LOCK(lockConnections)
{
CHECK_ERROR(started, L"NetworkProtocolChannelServer has not started.");
CHECK_ERROR(!stopped, L"NetworkProtocolChannelServer has stopped.");
}
}
auto networkProtocolClient = localClient.Cast();
CHECK_ERROR(networkProtocolClient, L"NetworkProtocolChannelServer::ConnectLocalClient needs a NetworkProtocolLocalChannelClient.");
if (networkProtocolClient->GetStatus() == ClientStatus::Connected || networkProtocolClient->GetStatus() == ClientStatus::Disconnected)
{
return -1;
}
auto&& channels = networkProtocolClient->GetChannels();
for (auto&& channelName : channels.Keys())
{
BaseChannel::ValidateChannelName(channelName);
auto index = channels.Keys().IndexOf(channelName);
CHECK_ERROR(channels.Values()[index], L"NetworkProtocolChannelServer::ConnectLocalClient needs valid local channels.");
}
vint assignedClientId = -1;
{
SPIN_LOCK(lockConnections)
{
assignedClientId = nextClientId++;
}
}
if (OnClientConnected(assignedClientId, channels.Keys()) == WaitForClientResult::Reject)
{
return -1;
}
if (!networkProtocolClient->ConnectLocalServer(this, assignedClientId))
{
return -1;
}
bool connected = false;
{
SPIN_LOCK(lockConnections)
{
if (!stopped)
{
localClients.Add(assignedClientId, networkProtocolClient);
for (auto&& channelName : channels.Keys())
{
clientChannels.Add(assignedClientId, channelName);
}
connected = true;
}
}
}
if (!connected)
{
networkProtocolClient->NotifyDisconnected();
return -1;
}
networkProtocolClient->NotifyLocalConnected();
return assignedClientId;
}
bool IsLocalClient(vint clientId) override
{
bool result = false;
SPIN_LOCK(lockConnections)
{
result = localClients.Keys().Contains(clientId);
}
return result;
}
bool DisconnectClient(vint clientId) override
{
Ptr connection;
Ptr localClient;
{
SPIN_LOCK(lockConnections)
{
if (connections.Keys().Contains(clientId))
{
connection = connections[clientId];
connections.Remove(clientId);
clientChannels.Remove(clientId);
}
else if (localClients.Keys().Contains(clientId))
{
localClient = localClients[clientId];
localClients.Remove(clientId);
clientChannels.Remove(clientId);
}
}
}
if (connection)
{
connection->connection->Stop();
OnClientDisconnected(clientId);
return true;
}
if (localClient)
{
NotifyLocalClientDisconnected(localClient);
OnClientDisconnected(clientId);
return true;
}
return false;
}
const ClientIdList& GetClientIds() override
{
return clientChannels.Keys();
}
const ClientChannelMap& GetClientChannels() override
{
return clientChannels;
}
void BroadcastError(const WString& errorMessage) override
{
collections::List> targetConnections;
collections::List> targetLocalClients;
{
SPIN_LOCK(lockConnections)
{
for (auto&& connection : connections.Values())
{
targetConnections.Add(connection);
}
for (auto&& localClient : localClients.Values())
{
targetLocalClients.Add(localClient);
}
}
}
for (auto&& connection : targetConnections)
{
connection->connection->SendString(NetworkPackage::ToString(NetworkPackage::Create({}, WString::Unmanaged(ErrorChannel), errorMessage)));
}
for (auto&& localClient : targetLocalClients)
{
localClient->OnReadError(errorMessage);
}
// Give transport clients a chance to consume the fatal package before closing.
Thread::Sleep(200);
Stop();
}
void Stop() override
{
collections::List> stoppingConnections;
collections::List> stoppingPendingConnections;
collections::List stoppingLocalClientIds;
collections::List> stoppingLocalClients;
bool shouldStop = false;
{
SPIN_LOCK(lockConnections)
{
if (!stopped)
{
started = false;
stopped = true;
shouldStop = true;
for (auto&& connection : connections.Values())
{
stoppingConnections.Add(connection);
}
for (auto&& connection : pendingConnections)
{
stoppingPendingConnections.Add(connection);
}
for (auto&& clientId : localClients.Keys())
{
stoppingLocalClientIds.Add(clientId);
stoppingLocalClients.Add(localClients[clientId]);
}
connections.Clear();
localClients.Clear();
pendingConnections.Clear();
clientChannels.Clear();
}
}
}
if (shouldStop)
{
for (auto&& connection : stoppingPendingConnections)
{
connection->connection->Stop();
}
for (auto&& connection : stoppingConnections)
{
connection->connection->Stop();
OnClientDisconnected(connection->clientId);
}
for (vint i = 0; i < stoppingLocalClients.Count(); i++)
{
NotifyLocalClientDisconnected(stoppingLocalClients[i]);
OnClientDisconnected(stoppingLocalClientIds[i]);
}
}
}
bool IsStopped() override
{
bool result = false;
SPIN_LOCK(lockConnections)
{
result = stopped;
}
return result;
}
};
}
#endif
/***********************************************************************
.\STREAM\INTERFACES.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_INTERFACES
#define VCZH_STREAM_INTERFACES
namespace vl
{
namespace stream
{
///
///
/// Interface for streams.
///
///
/// Please notice that, even if you get a stream object, if [M:vl.stream.IStream.IsAvailable] returns false, all other methods cannot be used.
///
///
/// Not all methods are available for all types of streams.
/// Feature testing functions must be called before calling other methods, if it is not sure that what kind of stream is being operated against:
///
///
///
/// - Readable: A stream is readable if [M:vl.stream.IStream.CanRead] returns true.
/// - Peekable: A stream is peekable if [M:vl.stream.IStream.CanPeek] returns true.
/// - Writable: A stream is writable if [M:vl.stream.IStream.CanWrite] returns true.
/// - Seekable: A stream is readable if [M:vl.stream.IStream.CanSeek] returns true.
/// - Finite: A stream is finite if [M:vl.stream.IStream.IsLimited] returns true.
///
///
///
class IStream : public virtual Interface
{
public:
/// Test if the stream is readable.
/// Returns true if the stream is readable.
virtual bool CanRead()const=0;
/// Test if the stream is writable.
/// Returns true if the stream is writable.
virtual bool CanWrite()const=0;
/// Test if the stream is seekable.
/// Returns true if the stream is seekable.
virtual bool CanSeek()const=0;
/// Test if the stream is peekable.
/// Returns true if the stream is peekable.
virtual bool CanPeek()const=0;
/// Test if the content of the stream is finite. A writable stream can also be limited, it means that you can only write limited content to the stream.
/// Returns true if the content of the stream is finite.
virtual bool IsLimited()const=0;
/// Test if the stream is available. For example, if you create a readable [T:vl.stream.FileStream] giving a wrong file name, it will be unavailable.
/// Returns true if the stream is available.
virtual bool IsAvailable()const=0;
/// Close the stream, making the stream unavailable.
virtual void Close()=0;
/// Get the current position in the stream.
/// The position in the stream. Returns -1 if the stream is unavailable.
virtual pos_t Position()const=0;
/// Get the size of the content in this stream.
/// The size of the content in this stream. Returns -1 if the size is unsizable or unavailable.
virtual pos_t Size()const=0;
/// Step forward or backward from the current position. It will crash if the stream is unseekable or unavailable.
/// The length to step forward if it is a positive number. The length to step backward if it is a negative number
virtual void Seek(pos_t _size)=0;
/// Step forward from the beginning. It will crash if the stream is unseekable or unavailable.
/// The length to step forward.
virtual void SeekFromBegin(pos_t _size)=0;
/// Step backward from the end. It will crash if the stream is unseekable or unavailable.
/// The length to step backward.
virtual void SeekFromEnd(pos_t _size)=0;
/// Read from the current position and step forward. It will crash if the stream is unreadable or unavailable.
/// Returns the actual size of the content that has read. Returns 0 if a stream has no more data to read.
/// A buffer to store the content.
/// The size of the content that is expected to read.
virtual vint Read(void* _buffer, vint _size)=0;
/// Write to the current position and step forward. It will crash if the stream is unwritable or unavailable.
/// Returns the actual size of the content that has written. Returns 0 if a stream has not enough space to write.
/// A buffer storing the content to write.
/// The size of the content that is expected to write.
virtual vint Write(void* _buffer, vint _size)=0;
/// Read from the current position without stepping forward. It will crash if the stream is unpeekable or unavailable.
/// Returns the actual size of the content that is read. Returns 0 if a stream has no more data to read.
/// A buffer to store the content.
/// The size of the content that is expected to read.
virtual vint Peek(void* _buffer, vint _size)=0;
};
}
}
#endif
/***********************************************************************
.\ENCODING\ENCODING.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_ENCODING_ENCODING
#define VCZH_STREAM_ENCODING_ENCODING
namespace vl
{
namespace stream
{
/***********************************************************************
IEncoder and IDecoder
***********************************************************************/
/// Encoder interface. This interface defines a writable transformation from one stream to another stream. You can create a [T:vl.stream.EncoderStream] after you have an encoder.
class IEncoder : public Interface
{
public:
/// Set a target writable stream to receive data. transforms the content and write to this tream.
/// The target writable stream.
virtual void Setup(IStream* _stream)=0;
/// Stop the transformation, ensuring all content is written to the target stream.
virtual void Close()=0;
///
/// Transform content and write to the target stream.
/// This function could use caching to improve performance.
/// Please do not expect that all transformed content will be written to the target stream immediately.
///
///
/// Returns the actual size of the content that has written before transforming.
/// A successful write operation may only cache the data without actually write anything to the target stream.
///
/// A buffer storing the content to transform.
/// The expected size of the content in bytes in "_buffer" to use.
virtual vint Write(void* _buffer, vint _size)=0;
};
/// Decoder interface. This interface defines a readable transformation from one stream to another stream. You can create a [T:vl.stream.DecoderStream] after you have an decoder.
class IDecoder : public Interface
{
public:
///
/// Set a target readable stream.
/// reads from this tream and transform the content.
///
/// The target readable stream.
virtual void Setup(IStream* _stream)=0;
/// Stop the transformation.
virtual void Close()=0;
/// Read from the target stream and transform the content.
/// Returns the actual size of the content has read after transforming.
/// A buffer to store the content.
/// The expected size of the content in bytes in "_buffer" to receive.
virtual vint Read(void* _buffer, vint _size)=0;
};
/***********************************************************************
EncoderBase and DecoderBase
***********************************************************************/
/// Basic implementation of IEncoder.
class EncoderBase : public Object, public IEncoder
{
protected:
IStream* stream = nullptr;
public:
void Setup(IStream* _stream) override;
void Close() override;
};
/// Basic implementation of IDecoder.
class DecoderBase : public Object, public IDecoder
{
protected:
IStream* stream = nullptr;
public:
void Setup(IStream* _stream) override;
void Close() override;
};
}
}
#endif
/***********************************************************************
.\ENCODING\BASE64ENCODING.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_ENCODING_BASE64ENCODING
#define VCZH_STREAM_ENCODING_BASE64ENCODING
namespace vl
{
namespace stream
{
constexpr const vint Base64CycleBytes = 3;
constexpr const vint Base64CycleChars = 4;
/***********************************************************************
Utf8Base64Encoder
***********************************************************************/
class Utf8Base64Encoder : public EncoderBase
{
protected:
uint8_t cache[Base64CycleBytes];
vint cacheSize = 0;
void WriteBytesToCharArray(uint8_t* fromBytes, char8_t(&toChars)[Base64CycleChars], vint bytes);
bool WriteCycle(uint8_t*& reading, vint& _size);
bool WriteCache(uint8_t*& reading, vint& _size);
public:
vint Write(void* _buffer, vint _size) override;
void Close() override;
};
/***********************************************************************
Utf8Base64Decoder
***********************************************************************/
class Utf8Base64Decoder : public DecoderBase
{
protected:
uint8_t cache[Base64CycleBytes];
vint cacheSize = 0;
vint ReadBytesFromCharArray(char8_t(&fromChars)[Base64CycleChars], uint8_t* toBytes);
vint ReadCycle(uint8_t*& writing, vint& _size);
void ReadCache(uint8_t*& writing, vint& _size);
public:
vint Read(void* _buffer, vint _size) override;
};
}
}
#endif
/***********************************************************************
.\ENCODING\CHARFORMAT\BOMENCODING.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_ENCODING_CHARFORMAT_BOMENCODING
#define VCZH_STREAM_ENCODING_CHARFORMAT_BOMENCODING
namespace vl
{
namespace stream
{
/***********************************************************************
Bom
***********************************************************************/
/// Encoder to write text in a specified encoding. A BOM will be added at the beginning.
class BomEncoder : public Object, public IEncoder
{
public:
/// Text encoding.
enum Encoding
{
/// Multi-bytes character string.
Mbcs,
/// UTF-8. EF, BB, BF will be written before writing any text.
Utf8,
/// UTF-16. FF FE will be written before writing any text.
Utf16,
/// Big endian UTF-16. FE FF, BF will be written before writing any text.
Utf16BE
};
protected:
Encoding encoding;
IEncoder* encoder;
public:
/// Create an encoder with a specified encoding.
/// The specified encoding.
BomEncoder(Encoding _encoding);
~BomEncoder();
void Setup(IStream* _stream);
void Close();
vint Write(void* _buffer, vint _size);
};
/// Decoder to read text. This decoder depends on BOM at the beginning to decide the format of the input.
class BomDecoder : public Object, public IDecoder
{
private:
class BomStream : public Object, public IStream
{
protected:
IStream* stream;
char bom[3];
vint bomLength;
vint bomPosition;
public:
BomStream(IStream* _stream, char* _bom, vint _bomLength);
bool CanRead()const;
bool CanWrite()const;
bool CanSeek()const;
bool CanPeek()const;
bool IsLimited()const;
bool IsAvailable()const;
void Close();
pos_t Position()const;
pos_t Size()const;
void Seek(pos_t _size);
void SeekFromBegin(pos_t _size);
void SeekFromEnd(pos_t _size);
vint Read(void* _buffer, vint _size);
vint Write(void* _buffer, vint _size);
vint Peek(void* _buffer, vint _size);
};
protected:
IDecoder* decoder;
IStream* stream;
public:
/// Create an decoder, BOM will be consumed before reading any text.
BomDecoder();
~BomDecoder();
void Setup(IStream* _stream);
void Close();
vint Read(void* _buffer, vint _size);
};
}
}
#endif
/***********************************************************************
.\ENCODING\CHARFORMAT\MBCSENCODING.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_ENCODING_CHARFORMAT_MBCSENCODING
#define VCZH_STREAM_ENCODING_CHARFORMAT_MBCSENCODING
namespace vl
{
namespace stream
{
/***********************************************************************
MbcsEncoder
***********************************************************************/
/// Encoder to write text in the local code page.
class MbcsEncoder : public EncoderBase
{
protected:
vuint8_t cacheBuffer[sizeof(char32_t)];
vint cacheSize = 0;
vint WriteString(wchar_t* _buffer, vint chars);
public:
vint Write(void* _buffer, vint _size) override;
};
/***********************************************************************
MbcsDecoder
***********************************************************************/
/// Decoder to read text in the local code page.
class MbcsDecoder : public DecoderBase
{
protected:
vuint8_t cacheBuffer[sizeof(wchar_t)];
vint cacheSize = 0;
vint ReadString(wchar_t* _buffer, vint chars);
public:
vint Read(void* _buffer, vint _size) override;
};
}
}
#endif
/***********************************************************************
.\ENCODING\CHARFORMAT\UTFENCODING.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_ENCODING_CHARFORMAT_UTFENCODING
#define VCZH_STREAM_ENCODING_CHARFORMAT_UTFENCODING
namespace vl
{
namespace stream
{
/***********************************************************************
UtfStreamConsumer
***********************************************************************/
template
class UtfStreamConsumer : public Object
{
protected:
IStream* stream = nullptr;
T Consume()
{
T c;
vint size = stream->Read(&c, sizeof(c));
if (size != sizeof(c)) return 0;
return c;
}
public:
void Setup(IStream* _stream)
{
stream = _stream;
}
bool HasIllegalChar() const
{
return false;
}
};
template
class UtfStreamConsumerApiRedirection : public Object
{
private:
T& internalConsumer;
public:
UtfStreamConsumerApiRedirection(T& _internalConsumer)
: internalConsumer(_internalConsumer)
{
}
void Setup(IStream* _stream)
{
internalConsumer.Setup(_stream);
}
encoding::UtfCharCluster SourceCluster() const
{
return internalConsumer.SourceCluster();
}
};
/***********************************************************************
UtfStreamToStreamReader
***********************************************************************/
template
using UtfStreamToStreamReader = encoding::UtfToUtfReaderBase, UtfStreamConsumerApiRedirection>;
/***********************************************************************
Unicode General
***********************************************************************/
template
struct MaxPossibleCodePoints
{
static const vint Value = encoding::UtfConversion::BufferLength;
};
template<>
struct MaxPossibleCodePoints
{
static const vint Value = 1;
};
template
class UtfGeneralEncoder : public EncoderBase
{
using TStringRangeReader = encoding::UtfStringRangeToStringRangeReader;
protected:
vuint8_t cacheBuffer[sizeof(TExpect) * MaxPossibleCodePoints::Value];
vint cacheSize = 0;
public:
vint Write(void* _buffer, vint _size) override;
};
template
class UtfGeneralDecoder : public DecoderBase
{
using TStreamReader = UtfStreamToStreamReader;
protected:
vuint8_t cacheBuffer[sizeof(TExpect)];
vint cacheSize = 0;
TStreamReader reader;
public:
void Setup(IStream* _stream) override;
vint Read(void* _buffer, vint _size) override;
};
/***********************************************************************
Unicode General (without conversion)
***********************************************************************/
template
class UtfGeneralEncoder : public EncoderBase
{
public:
vint Write(void* _buffer, vint _size) override;
};
template
class UtfGeneralDecoder : public DecoderBase
{
public:
vint Read(void* _buffer, vint _size) override;
};
#if defined VCZH_WCHAR_UTF16
template<>
class UtfGeneralEncoder : public UtfGeneralEncoder {};
template<>
class UtfGeneralEncoder : public UtfGeneralEncoder {};
#elif defined VCZH_WCHAR_UTF32
template<>
class UtfGeneralEncoder : public UtfGeneralEncoder {};
template<>
class UtfGeneralEncoder : public UtfGeneralEncoder {};
#endif
/***********************************************************************
Unicode General (extern templates)
***********************************************************************/
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralEncoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
extern template class UtfGeneralDecoder;
}
}
#endif
/***********************************************************************
.\ENCODING\CHARFORMAT\CHARFORMAT.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_ENCODING_CHARFORMAT
#define VCZH_STREAM_ENCODING_CHARFORMAT
namespace vl
{
namespace stream
{
/***********************************************************************
Utf-8
***********************************************************************/
/// Encoder to write UTF-8 text.
class Utf8Encoder : public UtfGeneralEncoder {};
/// Decoder to read UTF-8 text.
class Utf8Decoder : public UtfGeneralDecoder {};
/***********************************************************************
Utf-16
***********************************************************************/
/// Encoder to write UTF-16 text.
class Utf16Encoder : public UtfGeneralEncoder {};
/// Decoder to read UTF-16 text.
class Utf16Decoder : public UtfGeneralDecoder {};
/***********************************************************************
Utf-16BE
***********************************************************************/
/// Encoder to write big endian UTF-16 to.
class Utf16BEEncoder : public UtfGeneralEncoder {};
/// Decoder to read big endian UTF-16 text.
class Utf16BEDecoder : public UtfGeneralDecoder {};
/***********************************************************************
Utf-32
***********************************************************************/
/// Encoder to write UTF-8 text.
class Utf32Encoder : public UtfGeneralEncoder {};
/// Decoder to read UTF-8 text.
class Utf32Decoder : public UtfGeneralDecoder {};
/***********************************************************************
Encoding Test
***********************************************************************/
/// Guess the text encoding in a buffer.
/// The buffer to guess.
/// Size of the buffer in bytes.
/// Returns the most possible encoding.
/// Returns true if the BOM information is at the beginning of the buffer.
extern void TestEncoding(unsigned char* buffer, vint size, BomEncoder::Encoding& encoding, bool& containsBom);
}
}
#endif
/***********************************************************************
.\ENCODING\LZWENCODING.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_ENCODING_LZWENCODING
#define VCZH_STREAM_ENCODING_LZWENCODING
namespace vl
{
namespace stream
{
/***********************************************************************
Compression
***********************************************************************/
namespace lzw
{
static const vint BufferSize = 1024;
static const vint MaxDictionarySize = 1 << 24;
struct Code
{
typedef collections::PushOnlyAllocator CodeAllocator;
typedef collections::ByteObjectMap::Allocator MapAllocator;
vuint8_t byte = 0;
vint code = -1;
Code* parent = 0;
vint size = 0;
collections::ByteObjectMap children;
};
}
class LzwBase : public Object
{
protected:
lzw::Code::CodeAllocator codeAllocator;
lzw::Code::MapAllocator mapAllocator;
lzw::Code* root;
vint eofIndex = -1;
vint nextIndex = 0;
vint indexBits = 1;
void UpdateIndexBits();
lzw::Code* CreateCode(lzw::Code* parent, vuint8_t byte);
LzwBase();
LzwBase(bool (&existingBytes)[256]);
~LzwBase();
};
/// An encoder to compress data using the Lzw algorithm.
///
/// You are not recommended to compress data more than 1 mega bytes at once using the encoder directly.
/// and is recommended.
///
class LzwEncoder : public LzwBase, public EncoderBase
{
protected:
vuint8_t buffer[lzw::BufferSize];
vint bufferUsedBits = 0;
lzw::Code* prefix;
void Flush();
void WriteNumber(vint number, vint bitSize);
public:
/// Create an encoder.
LzwEncoder();
/// Create an encoder, specifying what bytes will never appear in the data to compress.
///
/// A filter array
/// If existingBytes[x] == true, it means x will possibly appear.
/// If existingBytes[x] == false, it means x will never appear.
///
///
/// The behavior is undefined, if existingBytes[x] == false, but byte x is actually in the data to compress.
///
LzwEncoder(bool (&existingBytes)[256]);
~LzwEncoder();
void Close()override;
vint Write(void* _buffer, vint _size)override;
};
/// An decoder to decompress data using the Lzw algorithm.
///
/// You are not recommended to compress data more than 1 mega bytes at once using the encoder directly.
/// and is recommended.
///
class LzwDecoder :public LzwBase, public DecoderBase
{
protected:
collections::List dictionary;
lzw::Code* lastCode = 0;
vuint8_t inputBuffer[lzw::BufferSize];
vint inputBufferSize = 0;
vint inputBufferUsedBits = 0;
collections::Array outputBuffer;
vint outputBufferSize = 0;
vint outputBufferUsedBytes = 0;
bool ReadNumber(vint& number, vint bitSize);
void PrepareOutputBuffer(vint size);
void ExpandCodeToOutputBuffer(lzw::Code* code);
public:
/// Create a decoder.
LzwDecoder();
/// Create an encoder, specifying what bytes will never appear in the decompressed data.
///
/// A filter array
/// If existingBytes[x] == true, it means x will possibly appear.
/// If existingBytes[x] == false, it means x will never appear.
///
///
/// The array "existingBytes" should exactly match the one given to .
///
LzwDecoder(bool (&existingBytes)[256]);
~LzwDecoder();
vint Read(void* _buffer, vint _size)override;
};
/***********************************************************************
Helper Functions
***********************************************************************/
/// Copy data from a readable input stream to a writable output stream.
/// Data copied in bytes.
/// The readable input stream.
/// The writable output stream.
extern vint CopyStream(stream::IStream& inputStream, stream::IStream& outputStream);
/// Compress data from a readable input stream to a writable output stream.
/// Data copied in bytes.
/// The readable input stream.
/// The writable output stream.
///
/// Data is compressed in multiple batches,
/// the is expected output stream to have data in multiple parts.
/// In each part, the first 4 bytes is the data before compression in bytes.
/// the rest is the compressed data.
///
///
extern void CompressStream(stream::IStream& inputStream, stream::IStream& outputStream);
/// Decompress data from a readable input stream (with compressed data) to a writable output stream (with uncompressed data).
/// Data copied in bytes.
/// The readable input stream.
/// The writable output stream.
///
/// Data is compressed in multiple batches,
/// the is expected input stream to have data in multiple parts.
/// In each part, the first 4 bytes is the data before compression in bytes.
/// the rest is the compressed data.
///
///
extern void DecompressStream(stream::IStream& inputStream, stream::IStream& outputStream);
}
}
#endif
/***********************************************************************
.\STREAM\BROADCASTSTREAM.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_BROADCASTSTREAM
#define VCZH_STREAM_BROADCASTSTREAM
namespace vl
{
namespace stream
{
/// A writable stream that copy the written content to multiple output streams.
///
/// When writing happens, the boreadcast stream will only performance one write attempt to each output stream.
///
class BroadcastStream : public Object, public virtual IStream
{
typedef collections::List StreamList;
protected:
bool closed;
pos_t position;
StreamList streams;
public:
/// Create a boradcast stream.
BroadcastStream();
~BroadcastStream();
///
/// Get the list of output streams.
/// You can change this list to subscribe or unsubscribe.
///
/// The list of output streams.
StreamList& Targets();
bool CanRead()const;
bool CanWrite()const;
bool CanSeek()const;
bool CanPeek()const;
bool IsLimited()const;
bool IsAvailable()const;
void Close();
pos_t Position()const;
pos_t Size()const;
void Seek(pos_t _size);
void SeekFromBegin(pos_t _size);
void SeekFromEnd(pos_t _size);
vint Read(void* _buffer, vint _size);
vint Write(void* _buffer, vint _size);
vint Peek(void* _buffer, vint _size);
};
}
}
#endif
/***********************************************************************
.\STREAM\CACHESTREAM.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_CACHESTREAM
#define VCZH_STREAM_CACHESTREAM
namespace vl
{
namespace stream
{
///
///
/// A potentially readable, peekable, writable, seekable and finite stream that creates on another stream.
/// Each feature is available if the target stream has the same feature.
///
///
/// When you read from the cache strema,
/// it will read a specified size of content from the target stream at once and cache,
/// reducing the number of operations on the target stream.
///
///
/// When you write to the cache stream,
/// it will cache all the data to write,
/// and write to the target stream after the cache is full,
/// reducing the number of operations on the target stream.
///
///
class CacheStream : public Object, public virtual IStream
{
protected:
IStream* target;
vint block;
pos_t start;
pos_t position;
char* buffer;
vint dirtyStart;
vint dirtyLength;
vint availableLength;
pos_t operatedSize;
void Flush();
void Load(pos_t _position);
vint InternalRead(void* _buffer, vint _size);
vint InternalWrite(void* _buffer, vint _size);
public:
/// Create a cache stream from a target stream.
/// The target stream.
/// Size of the cache.
CacheStream(IStream& _target, vint _block=65536);
~CacheStream();
bool CanRead()const;
bool CanWrite()const;
bool CanSeek()const;
bool CanPeek()const;
bool IsLimited()const;
bool IsAvailable()const;
void Close();
pos_t Position()const;
pos_t Size()const;
void Seek(pos_t _size);
void SeekFromBegin(pos_t _size);
void SeekFromEnd(pos_t _size);
vint Read(void* _buffer, vint _size);
vint Write(void* _buffer, vint _size);
vint Peek(void* _buffer, vint _size);
};
}
}
#endif
/***********************************************************************
.\STREAM\ENCODINGSTREAM.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_ENCODINGSTREAM
#define VCZH_STREAM_ENCODINGSTREAM
namespace vl
{
namespace stream
{
/***********************************************************************
Encoding Related
***********************************************************************/
/// Encoder stream, a writable and potentially finite stream using [T:vl.stream.IEncoder] to transform content.
class EncoderStream : public virtual IStream
{
protected:
IStream* stream;
IEncoder* encoder;
pos_t position;
public:
/// Create en encoder stream.
/// The output stream to write.
/// The encoder to transform content.
EncoderStream(IStream& _stream, IEncoder& _encoder);
~EncoderStream();
bool CanRead()const;
bool CanWrite()const;
bool CanSeek()const;
bool CanPeek()const;
bool IsLimited()const;
bool IsAvailable()const;
void Close();
pos_t Position()const;
pos_t Size()const;
void Seek(pos_t _size);
void SeekFromBegin(pos_t _size);
void SeekFromEnd(pos_t _size);
vint Read(void* _buffer, vint _size);
vint Write(void* _buffer, vint _size);
vint Peek(void* _buffer, vint _size);
};
/// Decoder stream, a readable and potentially finite stream using [T:vl.stream.IDecoder] to transform content.
class DecoderStream : public virtual IStream
{
protected:
IStream* stream;
IDecoder* decoder;
pos_t position;
public:
/// Create a decoder stream.
/// The input stream to read.
/// The decoder to transform content.
DecoderStream(IStream& _stream, IDecoder& _decoder);
~DecoderStream();
bool CanRead()const;
bool CanWrite()const;
bool CanSeek()const;
bool CanPeek()const;
bool IsLimited()const;
bool IsAvailable()const;
void Close();
pos_t Position()const;
pos_t Size()const;
void Seek(pos_t _size);
void SeekFromBegin(pos_t _size);
void SeekFromEnd(pos_t _size);
vint Read(void* _buffer, vint _size);
vint Write(void* _buffer, vint _size);
vint Peek(void* _buffer, vint _size);
};
}
}
#endif
/***********************************************************************
.\STREAM\FILESTREAM.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_FILESTREAM
#define VCZH_STREAM_FILESTREAM
#include
namespace vl
{
namespace stream
{
/// Platform-specific file stream implementation interface.
class IFileStreamImpl : public virtual Interface
{
public:
virtual bool Open() = 0;
virtual void Close() = 0;
virtual pos_t Position() const = 0;
virtual pos_t Size() const = 0;
virtual void Seek(pos_t _size) = 0;
virtual void SeekFromBegin(pos_t _size) = 0;
virtual void SeekFromEnd(pos_t _size) = 0;
virtual vint Read(void* _buffer, vint _size) = 0;
virtual vint Write(void* _buffer, vint _size) = 0;
virtual vint Peek(void* _buffer, vint _size) = 0;
};
/// A file stream. If the given file name is not working, the stream could be unavailable.
class FileStream : public Object, public virtual IStream
{
public:
/// Access to the file.
enum AccessRight
{
/// The file is opened to read, making this stream readable, seekable and finite.
ReadOnly,
/// The file is opened to write, making this stream writable.
WriteOnly,
/// The file is opened to both read and write, making this stream readable, seekable and writable.
ReadWrite
};
protected:
AccessRight accessRight;
Ptr impl;
public:
/// Create a file stream from a given file name.
/// The file to operate.
/// Expected operations on the file.
FileStream(const WString& fileName, AccessRight _accessRight);
~FileStream();
bool CanRead()const;
bool CanWrite()const;
bool CanSeek()const;
bool CanPeek()const;
bool IsLimited()const;
bool IsAvailable()const;
void Close();
pos_t Position()const;
pos_t Size()const;
void Seek(pos_t _size);
void SeekFromBegin(pos_t _size);
void SeekFromEnd(pos_t _size);
vint Read(void* _buffer, vint _size);
vint Write(void* _buffer, vint _size);
vint Peek(void* _buffer, vint _size);
};
}
}
#endif
/***********************************************************************
.\FILESYSTEM.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_FILESYSTEM
#define VCZH_FILESYSTEM
namespace vl
{
namespace filesystem
{
/// Absolute file path.
class FilePath : public Object
{
friend class LinuxFileSystemImpl;
friend class WindowsFileSystemImpl;
protected:
WString fullPath;
static void NormalizeDelimiters(collections::Array& buffer);
static void TrimLastDelimiter(WString& fullPath);
void Initialize();
static void GetPathComponents(WString path, collections::List& components);
static WString ComponentsToPath(const collections::List& components);
public:
/// Get the delimiter character used in a file path.
static wchar_t GetPathDelimiter();
/// Create a root path.
/// returns different values for root path on different platforms. Do not rely on the value.
FilePath();
/// Create a file path.
/// Content of the file path. If it is a relative path, it will be converted to an absolute path.
FilePath(const WString& _filePath);
/// Create a file path.
/// Content of the file path. If it is a relative path, it will be converted to an absolute path.
FilePath(const wchar_t* _filePath);
/// Copy a file path.
/// The file path to copy.
FilePath(const FilePath& _filePath);
~FilePath() = default;
std::strong_ordering operator<=>(const FilePath& path)const { return fullPath <=> path.fullPath; }
bool operator==(const FilePath& path)const { return fullPath == path.fullPath; }
/// Concat an absolute path and a relative path.
/// The result absolute path.
/// The relative path to concat.
FilePath operator/(const WString& relativePath)const;
/// Test if the file path is a file.
/// Returns true if the file path is a file.
bool IsFile()const;
/// Test if the file path is a folder.
/// Returns true if the file path is a folder.
/// In Windows, a drive is also considered a folder.
bool IsFolder()const;
/// Test if the file path is a the root of all file system objects.
/// Returns true if the file path is the root of all file system objects.
bool IsRoot()const;
/// Get the last piece of names in the file path.
/// The last piece of names in the file path.
WString GetName()const;
/// Get the containing folder of this file path.
/// The containing folder.
FilePath GetFolder()const;
/// Get the content of the file path.
/// The content of the file path.
WString GetFullPath()const;
/// Calculate the relative path based on a specified referencing folder.
/// The relative path.
/// The referencing folder.
WString GetRelativePathFor(const FilePath& _filePath)const;
};
/// A file.
class File : public Object
{
private:
FilePath filePath;
public:
/// Create an empty reference. An empty reference does not refer to any file.
File() = default;
/// Create a reference to a specified file. The file is not required to exist.
/// The specified file.
File(const FilePath& _filePath);
~File() = default;
/// Get the file path of the file.
/// The file path.
const FilePath& GetFilePath()const;
/// Get the content of a text file with encoding testing.
/// Returns true if this operation succeeded.
/// Returns the content of the file.
/// Returns the encoding of the file.
/// Returns true if there is a BOM in the file.
bool ReadAllTextWithEncodingTesting(WString& text, stream::BomEncoder::Encoding& encoding, bool& containsBom);
/// Get the content of a text file. If there is no BOM in the file, the encoding is assumed to be aligned to the current code page.
/// The content of the file.
WString ReadAllTextByBom()const;
/// Get the content of a text file.
/// Returns true if this operation succeeded.
/// The content of the file.
bool ReadAllTextByBom(WString& text)const;
/// Get the content of a text file by lines.
/// Returns true if this operation succeeded.
/// The content of the file by lines.
///
/// Lines could be separated by either CRLF or LF.
/// A text file is not required to ends with CRLF.
/// If the last character of the file is LF,
/// the last line is the line before LF.
///
bool ReadAllLinesByBom(collections::List& lines)const;
/// Write text to the file.
/// Returns true if this operation succeeded.
/// The text to write.
/// Set to true to add a corresponding BOM at the beginning of the file according to the encoding, the default value is true.
/// The text encoding, the default encoding is UTF-16.
bool WriteAllText(const WString& text, bool bom = true, stream::BomEncoder::Encoding encoding = stream::BomEncoder::Utf16);
/// Write text to the file.
/// Returns true if this operation succeeded.
/// The text to write, with CRLF appended after all lines.
/// Set to true to add a corresponding BOM at the beginning of the file according to the encoding, the default value is true.
/// The text encoding, the default encoding is UTF-16.
bool WriteAllLines(collections::List& lines, bool bom = true, stream::BomEncoder::Encoding encoding = stream::BomEncoder::Utf16);
/// Test does the file exist or not.
/// Returns true if the file exists.
bool Exists()const;
/// Delete the file.
/// Returns true if this operation succeeded.
/// This function could return before the file is actually deleted.
bool Delete()const;
/// Rename the file.
/// Returns true if this operation succeeded.
/// The new file name.
bool Rename(const WString& newName)const;
};
/// A folder.
/// In Windows, a drive is also considered a folder.
class Folder : public Object
{
private:
FilePath filePath;
bool CreateNonRecursively()const;
bool DeleteNonRecursively()const;
public:
/// Create a reference to the root folder.
Folder() = default;
/// Create a reference to a specified folder. The folder is not required to exist.
/// The specified folder.
Folder(const FilePath& _filePath);
~Folder() = default;
/// Get the file path of the folder.
/// The file path.
const FilePath& GetFilePath()const;
/// Get all folders in this folder.
/// Returns true if this operation succeeded.
/// All folders.
/// In Windows, drives are considered sub folders in the root folder.
bool GetFolders(collections::List& folders)const;
/// Get all files in this folder.
/// Returns true if this operation succeeded.
/// All files.
bool GetFiles(collections::List& files)const;
/// Test does the folder exist or not.
/// Returns true if the folder exists.
bool Exists()const;
/// Create the folder.
/// Returns true if this operation succeeded.
/// Set to true to create all levels of containing folders if they do not exist.
///
/// This function could return before the folder is actually created.
/// If "recursively" is false, this function will only attempt to create the specified folder directly,
/// it fails if the containing folder does not exist.
///
bool Create(bool recursively)const;
/// Delete the folder.
/// Returns true if this operation succeeded.
/// Set to true to delete everything in the folder.
/// This function could return before the folder is actually deleted.
bool Delete(bool recursively)const;
/// Rename the folder.
/// Returns true if this operation succeeded.
/// The new folder name.
bool Rename(const WString& newName)const;
};
/// Platform-specific file system implementation interface.
class IFileSystemImpl : public virtual feature_injection::IFeatureImpl
{
public:
// FilePath operations
virtual wchar_t GetPathDelimiter() const = 0;
virtual const wchar_t* GetCompatibleDelimiters() const = 0;
virtual WString ConcatPath(const WString& fullPath, const WString& relativePath) const = 0;
virtual void Initialize(WString& fullPath) const = 0;
virtual bool IsFile(const WString& fullPath) const = 0;
virtual bool IsFolder(const WString& fullPath) const = 0;
virtual bool IsRoot(const WString& fullPath) const = 0;
virtual WString GetRelativePathFor(const WString& fromPath, const WString& toPath) const = 0;
// File operations
virtual bool FileDelete(const FilePath& filePath) const = 0;
virtual bool FileRename(const FilePath& filePath, const WString& newName) const = 0;
// Folder operations
virtual bool GetFolders(const FilePath& folderPath, collections::List& folders) const = 0;
virtual bool GetFiles(const FilePath& folderPath, collections::List& files) const = 0;
virtual bool CreateFolder(const FilePath& folderPath) const = 0;
virtual bool DeleteFolder(const FilePath& folderPath) const = 0;
virtual bool FolderRename(const FilePath& folderPath, const WString& newName) const = 0;
// Stream operations
virtual Ptr GetFileStreamImpl(const WString& fileName, stream::FileStream::AccessRight accessRight) const = 0;
};
extern void InjectFileSystemImpl(IFileSystemImpl* impl);
extern void EjectFileSystemImpl(IFileSystemImpl* impl);
}
}
#endif
/***********************************************************************
.\STREAM\MEMORYSTREAM.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_MEMORYSTREAM
#define VCZH_STREAM_MEMORYSTREAM
namespace vl
{
namespace stream
{
/// A readable, peekable, writable and seekable stream that creates on a buffer.
class MemoryStream : public Object, public virtual IStream
{
protected:
vint block;
char* buffer;
vint size;
vint position;
vint capacity;
void PrepareSpace(vint totalSpace);
public:
/// Create a memory stream.
///
/// Size for each allocation.
/// When the allocated buffer is not big enough for writing,
/// the buffer will be rebuilt with an extension of "_block" in bytes.
///
MemoryStream(vint _block=65536);
~MemoryStream();
bool CanRead()const;
bool CanWrite()const;
bool CanSeek()const;
bool CanPeek()const;
bool IsLimited()const;
bool IsAvailable()const;
void Close();
pos_t Position()const;
pos_t Size()const;
void Seek(pos_t _size);
void SeekFromBegin(pos_t _size);
void SeekFromEnd(pos_t _size);
vint Read(void* _buffer, vint _size);
vint Write(void* _buffer, vint _size);
vint Peek(void* _buffer, vint _size);
void* GetInternalBuffer();
};
}
}
#endif
/***********************************************************************
.\STREAM\ACCESSOR.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_ACCESSOR
#define VCZH_STREAM_ACCESSOR
namespace vl
{
namespace stream
{
/***********************************************************************
Text Related
***********************************************************************/
/// Text reader. All line breaks are normalized to CRLF regardless whatever in the input stream.
/// The character type.
template
class TextReader_ : public Object
{
public:
NOT_COPYABLE(TextReader_);
TextReader_() = default;
/// Test does the reader reach the end or not.
/// Returns true if the reader reaches the end.
virtual bool IsEnd()=0;
/// Read a single character.
/// The character.
virtual T ReadChar()=0;
/// Read a string of a specified size in characters.
/// The read string. It could be shorter than the expected length if the reader reaches the end.
/// Expected length of the string to read.
virtual ObjectString ReadString(vint length);
/// Read a string until a line breaks is reached.
/// The string without the line break. If the reader reaches the end, it returns an empty string.
virtual ObjectString ReadLine();
/// Read everying remain.
/// The read string.
virtual ObjectString ReadToEnd();
};
/// Text writer.
/// The character type.
template
class TextWriter_ : public Object
{
public:
NOT_COPYABLE(TextWriter_);
TextWriter_() = default;
/// Write a single character.
/// The character to write.
virtual void WriteChar(T c)=0;
/// Write a string.
/// Buffer of the string to write.
/// Size of the string in characters, not including the zero terminator.
virtual void WriteString(const T* string, vint charCount);
/// Write a string.
/// Buffer of the zero terminated string to write.
virtual void WriteString(const T* string);
/// Write a string.
/// The string to write.
virtual void WriteString(const ObjectString& string);
/// Write a string with a CRLF.
/// Buffer to the string to write.
/// Size of the string in characters, not including the zero terminator.
virtual void WriteLine(const T* string, vint charCount);
/// Write a string with a CRLF.
/// Buffer to the zero terminated string to write.
virtual void WriteLine(const T* string);
/// Write a string with a CRLF.
/// The string to write.
virtual void WriteLine(const ObjectString& string);
};
/// Text reader from a string.
/// The character type.
template
class StringReader_ : public TextReader_
{
protected:
ObjectString string;
vint current;
bool lastCallIsReadLine;
void PrepareIfLastCallIsReadLine();
public:
/// Create a text reader.
/// The string to read.
StringReader_(const ObjectString& _string);
bool IsEnd();
T ReadChar();
ObjectString ReadString(vint length);
ObjectString ReadLine();
ObjectString ReadToEnd();
};
///
/// Text reader from a stream storing characters in code point.
///
/// The character type.
///
/// To specify the encoding in the input stream,
/// you are recommended to create a with a implementation,
/// like , , , or .
///
///
template
class StreamReader_ : public TextReader_
{
protected:
IStream* stream;
public:
/// Create a text reader.
/// The stream to read.
StreamReader_(IStream& _stream);
bool IsEnd();
T ReadChar();
};
///
/// Text reader from a stream storing characters in code point.
///
/// The character type.
///
/// To specify the encoding in the input stream,
/// you are recommended to create a with a implementation,
/// like , , , or .
///
///
template
class StreamWriter_ : public TextWriter_
{
protected:
IStream* stream;
public:
/// Create a text writer.
/// The stream to write.
StreamWriter_(IStream& _stream);
using TextWriter_::WriteString;
void WriteChar(T c);
void WriteString(const T* string, vint charCount);
};
/***********************************************************************
Extern Templates
***********************************************************************/
extern template class TextReader_;
extern template class TextReader_;
extern template class TextReader_;
extern template class TextReader_;
extern template class TextWriter_;
extern template class TextWriter_;
extern template class TextWriter_;
extern template class TextWriter_;
extern template class StringReader_;
extern template class StringReader_;
extern template class StringReader_;
extern template class StringReader_;
extern template class StreamReader_;
extern template class StreamReader_;
extern template class StreamReader_;
extern template class StreamReader_;
extern template class StreamWriter_;
extern template class StreamWriter_;
extern template class StreamWriter_;
extern template class StreamWriter_;
/***********************************************************************
Helper Functions
***********************************************************************/
using TextReader = TextReader_;
using TextWriter = TextWriter_;
using StringReader = StringReader_;
using StreamReader = StreamReader_;
using StreamWriter = StreamWriter_;
void WriteMonospacedEnglishTable(TextWriter& writer, collections::Array& tableByRow, vint rows, vint columns);
///
/// Build a big string using .
///
/// The type of the callback.
/// The built big string.
///
/// The callback to receive a big string.
/// The argument is a reference to a .
/// After the callback is executed, everything written to the writer will be returned from "GenerateToStream".
///
/// Size of the cache in bytes.
///
template
WString GenerateToStream(const TCallback& callback, vint block = 65536)
{
MemoryStream stream(block);
{
StreamWriter writer(stream);
callback(writer);
}
stream.SeekFromBegin(0);
{
StreamReader reader(stream);
return reader.ReadToEnd();
}
}
}
}
#endif
/***********************************************************************
.\STREAM\MEMORYWRAPPERSTREAM.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_MEMORYWRAPPERSTREAM
#define VCZH_STREAM_MEMORYWRAPPERSTREAM
namespace vl
{
namespace stream
{
/// A readable, peekable, writable, seekable and finite stream that creates on a buffer.
class MemoryWrapperStream : public Object, public virtual IStream
{
protected:
char* buffer;
vint size;
vint position;
public:
/// Create a memory wrapper stream.
/// The buffer to operate.
/// Size of the buffer in bytes.
MemoryWrapperStream(void* _buffer, vint _size);
~MemoryWrapperStream();
bool CanRead()const;
bool CanWrite()const;
bool CanSeek()const;
bool CanPeek()const;
bool IsLimited()const;
bool IsAvailable()const;
void Close();
pos_t Position()const;
pos_t Size()const;
void Seek(pos_t _size);
void SeekFromBegin(pos_t _size);
void SeekFromEnd(pos_t _size);
vint Read(void* _buffer, vint _size);
vint Write(void* _buffer, vint _size);
vint Peek(void* _buffer, vint _size);
};
}
}
#endif
/***********************************************************************
.\STREAM\RECORDERSTREAM.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_RECORDERSTREAM
#define VCZH_STREAM_RECORDERSTREAM
namespace vl
{
namespace stream
{
///
/// A readable stream that, reads from one stream, and copy everything that is read to another stream.
/// The stream is unavailable if one of the input stream or the output stream is unavailable.
/// The stream is readable, and potentially finite.
///
///
/// When reading happens, the recorder stream will only performance one write attempt to the output stream.
///
class RecorderStream : public Object, public virtual IStream
{
protected:
IStream* in;
IStream* out;
public:
/// Create a recorder stream.
///
/// The input stream.
/// This recorder stream is readable only when the input stream is readable
/// This recorder stream is finite only when the input stream is finite
///
///
/// The output stream.
///
RecorderStream(IStream& _in, IStream& _out);
~RecorderStream();
bool CanRead()const;
bool CanWrite()const;
bool CanSeek()const;
bool CanPeek()const;
bool IsLimited()const;
bool IsAvailable()const;
void Close();
pos_t Position()const;
pos_t Size()const;
void Seek(pos_t _size);
void SeekFromBegin(pos_t _size);
void SeekFromEnd(pos_t _size);
vint Read(void* _buffer, vint _size);
vint Write(void* _buffer, vint _size);
vint Peek(void* _buffer, vint _size);
};
}
}
#endif
/***********************************************************************
.\STREAM\SERIALIZATION.H
***********************************************************************/
/***********************************************************************
Author: Zihan Chen (vczh)
Licensed under https://github.com/vczh-libraries/License
***********************************************************************/
#ifndef VCZH_STREAM_SERIALIZATION
#define VCZH_STREAM_SERIALIZATION
namespace vl
{
namespace stream
{
/***********************************************************************
Serialization
***********************************************************************/
namespace internal
{
template
struct Reader
{
stream::IStream& input;
T context;
Reader(stream::IStream& _input)
:input(_input)
, context(nullptr)
{
}
};
template
struct Writer
{
stream::IStream& output;
T context;
Writer(stream::IStream& _output)
:output(_output)
, context(nullptr)
{
}
};
using ContextFreeReader = Reader;
using ContextFreeWriter = Writer;
template
struct Serialization
{
template
static void IO(TIO& io, T& value);
};
template
Reader& operator<<(Reader& reader, TValue& value)
{
Serialization::IO(reader, value);
return reader;
}
template
Writer& operator<<(Writer& writer, TValue& value)
{
Serialization::IO(writer, value);
return writer;
}
/***********************************************************************
Serialization (integers)
***********************************************************************/
template
struct Serialization_POD
{
template
static void IO(Reader& reader, T& value)
{
if (reader.input.Read(&value, sizeof(value)) != sizeof(value))
{
CHECK_FAIL(L"Deserialization failed.");
}
}
template
static void IO(Writer& writer, T& value)
{
if (writer.output.Write(&value, sizeof(value)) != sizeof(value))
{
CHECK_FAIL(L"Serialization failed.");
}
}
};
template
struct Serialization_DefaultConversion
{
static TValue ToValue(TData data)
{
return (TValue)data;
}
static TData FromValue(TValue value)
{
return (TData)value;
}
};
template>
struct Serialization_Conversion
{
template
static void IO(Reader& reader, TValue& value)
{
TData data;
Serialization::IO(reader, data);
value = TConversion::ToValue(data);
}
template
static void IO(Writer& writer, TValue& value)
{
TData data = TConversion::FromValue(value);
Serialization::IO(writer, data);
}
};
template<>
struct Serialization : Serialization_POD {};
template<>
struct Serialization : Serialization_POD {};
template<>
struct Serialization : Serialization_Conversion {};
template<>
struct Serialization : Serialization_Conversion {};
template<>
struct Serialization : Serialization_POD {};
template<>
struct Serialization : Serialization_POD {};
template<>
struct Serialization : Serialization_POD {};
template<>
struct Serialization : Serialization_POD {};
/***********************************************************************
Serialization (chars)
***********************************************************************/
template<>
struct Serialization : Serialization_Conversion {};
template<>
struct Serialization : Serialization_Conversion {};
template<>
struct Serialization : Serialization_Conversion {};
template<>
struct Serialization : Serialization_Conversion {};
template<>
struct Serialization : Serialization_Conversion {};
/***********************************************************************
Serialization (floats)
***********************************************************************/
template<>
struct Serialization : Serialization_POD {};
template<>
struct Serialization : Serialization_POD {};
template<>
struct Serialization : Serialization_Conversion>
{
static bool ToValue(vint8_t data)
{
return data == -1;
}
static vint8_t FromValue(bool value)
{
return value ? -1 : 0;
}
};
/***********************************************************************
Serialization (enum)
***********************************************************************/
template
requires(std::is_enum_v)
struct Serialization : Serialization_Conversion {};
/***********************************************************************
Serialization (strings)
***********************************************************************/
template<>
struct Serialization
{
template
static void IO(Reader& reader, U8String& value)
{
vint count = -1;
reader << count;
if (count > 0)
{
char8_t* buffer = new char8_t[count + 1];
MemoryWrapperStream stream(buffer, count);
reader << (IStream&)stream;
buffer[count] = 0;
value = U8String::TakeOver(buffer, count);
}
else
{
value = {};
}
}
template
static void IO(Writer& writer, U8String& value)
{
vint count = value.Length();
writer << count;
if (count > 0)
{
MemoryWrapperStream stream((void*)value.Buffer(), count);
writer << (IStream&)stream;
}
}
};
template<>
struct Serialization : Serialization_Conversion>
{
static WString ToValue(const U8String& data)
{
return u8tow(data);
}
static U8String FromValue(const WString& value)
{
return wtou8(value);
}
};
template<>
struct Serialization : Serialization_Conversion>
{
static U16String ToValue(const U8String& data)
{
return u8tou16(data);
}
static U8String FromValue(const U16String& value)
{
return u16tou8(value);
}
};
template<>
struct Serialization : Serialization_Conversion>
{
static U32String ToValue(const U8String& data)
{
return u8tou32(data);
}
static U8String FromValue(const U32String& value)
{
return u32tou8(value);
}
};
/***********************************************************************
Serialization (generic types)
***********************************************************************/
template
struct Serialization>
{
template
static void IO(Reader& reader, Ptr& value)
{
bool notNull = false;
reader << notNull;
if (notNull)
{
value = Ptr(new T);
Serialization::IO(reader, *value.Obj());
}
else
{
value = 0;
}
}
template
static void IO(Writer& writer, Ptr& value)
{
bool notNull = value;
writer << notNull;
if (notNull)
{
Serialization::IO(writer, *value.Obj());
}
}
};
template
struct Serialization>
{
template
static void IO(Reader& reader, Nullable& value)
{
bool notNull = false;
reader << notNull;
if (notNull)
{
T data;
Serialization::IO(reader, data);
value = Nullable(data);
}
else
{
value = Nullable();
}
}
template
static void IO(Writer& writer, Nullable& value)
{
bool notNull = value;
writer << notNull;
if (notNull)
{
T data = value.Value();
Serialization::IO(writer, data);
}
}
};
/***********************************************************************
Serialization (collections)
***********************************************************************/
template
struct Serialization>
{
template
static void IO(Reader& reader, collections::List& value)
{
vint32_t count = -1;
reader << count;
value.Clear();
for (vint i = 0; i < count; i++)
{
T t;
reader << t;
value.Add(t);
}
}
template
static void IO(Writer& writer, collections::List& value)
{
vint32_t count = (vint32_t)value.Count();
writer << count;
for (vint i = 0; i < count; i++)
{
writer << value[i];
}
}
};
template
struct Serialization