From 043758b1bb5116f038c5c597d17fe9524b92b3c2 Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Tue, 17 Oct 2023 16:47:45 +0300 Subject: [PATCH 1/6] Engine: store ScriptString in memory similarly to dynamic array This potentially allows to access any metadata while passing string pointer around the script functions. As a consequence: never wrap external buffer, always allocate your own. --- Engine/ac/audioclip.cpp | 2 - Engine/ac/button.cpp | 2 - Engine/ac/character.cpp | 2 - Engine/ac/dialog.cpp | 1 - Engine/ac/dynobj/cc_dynamicarray.cpp | 6 +-- Engine/ac/dynobj/cc_scriptobject.h | 6 --- Engine/ac/dynobj/cc_serializer.cpp | 3 +- Engine/ac/dynobj/dynobj_manager.cpp | 7 --- Engine/ac/dynobj/dynobj_manager.h | 4 -- Engine/ac/dynobj/scriptstring.cpp | 79 ++++++++++++++------------- Engine/ac/dynobj/scriptstring.h | 47 ++++++++++------ Engine/ac/file.cpp | 8 ++- Engine/ac/game.cpp | 1 - Engine/ac/global_api.cpp | 1 - Engine/ac/gui.cpp | 2 - Engine/ac/guicontrol.cpp | 2 - Engine/ac/hotspot.cpp | 2 - Engine/ac/inventoryitem.cpp | 2 - Engine/ac/label.cpp | 2 - Engine/ac/listbox.cpp | 2 - Engine/ac/object.cpp | 2 - Engine/ac/parser.cpp | 2 - Engine/ac/properties.cpp | 1 - Engine/ac/room.cpp | 2 - Engine/ac/scriptcontainers.cpp | 2 - Engine/ac/string.cpp | 81 ++++++++++++---------------- Engine/ac/string.h | 8 +-- Engine/ac/system.cpp | 2 - Engine/ac/textbox.cpp | 2 - Engine/game/game_init.cpp | 2 - Engine/plugin/agsplugin.cpp | 1 - Engine/script/cc_instance.cpp | 18 ++----- 32 files changed, 118 insertions(+), 186 deletions(-) diff --git a/Engine/ac/audioclip.cpp b/Engine/ac/audioclip.cpp index a89e5983e39..bd737d76f9b 100644 --- a/Engine/ac/audioclip.cpp +++ b/Engine/ac/audioclip.cpp @@ -104,8 +104,6 @@ ScriptAudioChannel* AudioClip_PlayOnChannel(ScriptAudioClip *clip, int chan, int #include "script/script_api.h" #include "script/script_runtime.h" -extern ScriptString myScriptStringImpl; - ScriptAudioClip *AudioClip_GetByName(const char *name) { return static_cast(ccGetScriptObjectAddress(name, ccDynamicAudioClip.GetType())); diff --git a/Engine/ac/button.cpp b/Engine/ac/button.cpp index 02607323b8f..97b54fc5ee0 100644 --- a/Engine/ac/button.cpp +++ b/Engine/ac/button.cpp @@ -320,8 +320,6 @@ void Button_SetTextAlignment(GUIButton *butt, int align) #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - // void | GUIButton *butt, int view, int loop, int speed, int repeat RuntimeScriptValue Sc_Button_Animate4(void *self, const RuntimeScriptValue *params, int32_t param_count) { diff --git a/Engine/ac/character.cpp b/Engine/ac/character.cpp index 736fd15b45f..d1ea276523e 100644 --- a/Engine/ac/character.cpp +++ b/Engine/ac/character.cpp @@ -3030,8 +3030,6 @@ PViewport FindNearestViewport(int charid) #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - CharacterInfo *Character_GetByName(const char *name) { return static_cast(ccGetScriptObjectAddress(name, ccDynamicCharacter.GetType())); diff --git a/Engine/ac/dialog.cpp b/Engine/ac/dialog.cpp index 753109aa3b7..8f2e59e3339 100644 --- a/Engine/ac/dialog.cpp +++ b/Engine/ac/dialog.cpp @@ -1330,7 +1330,6 @@ void do_conversation(int dlgnum) #include "ac/dynobj/cc_dialog.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; extern CCDialog ccDynamicDialog; ScriptDialog *Dialog_GetByName(const char *name) diff --git a/Engine/ac/dynobj/cc_dynamicarray.cpp b/Engine/ac/dynobj/cc_dynamicarray.cpp index 5cc70821276..127ae3e282a 100644 --- a/Engine/ac/dynobj/cc_dynamicarray.cpp +++ b/Engine/ac/dynobj/cc_dynamicarray.cpp @@ -14,7 +14,7 @@ #include "cc_dynamicarray.h" #include #include "ac/dynobj/dynobj_manager.h" -#include "util/memorystream.h" +#include "ac/dynobj/scriptstring.h" using namespace AGS::Common; @@ -58,7 +58,7 @@ size_t CCDynamicArray::CalcSerializeSize(const void *address) return hdr.TotalSize + FileHeaderSz; } -void CCDynamicArray::Serialize(const void *address, AGS::Common::Stream *out) +void CCDynamicArray::Serialize(const void *address, Stream *out) { const Header &hdr = GetHeader(address); out->WriteInt32(hdr.ElemCount); @@ -107,7 +107,7 @@ DynObjectRef DynamicArrayHelpers::CreateStringArray(const std::vector(arr.Obj); for (auto s : items) { - DynObjectRef str = stringClassImpl->CreateString(s); + DynObjectRef str = ScriptString::Create(s); // We must add reference count, because the string is going to be saved // within another object (array), not returned to script directly ccAddObjectReference(str.Handle); diff --git a/Engine/ac/dynobj/cc_scriptobject.h b/Engine/ac/dynobj/cc_scriptobject.h index 92b5afe7589..8c32149c69b 100644 --- a/Engine/ac/dynobj/cc_scriptobject.h +++ b/Engine/ac/dynobj/cc_scriptobject.h @@ -105,10 +105,4 @@ struct ICCObjectReader virtual void Unserialize(int32_t handle, const char *serializedData, int dataSize) = 0; }; -// The interface of a dynamic String allocator. -struct ICCStringClass -{ - virtual DynObjectRef CreateString(const char *fromText) = 0; -}; - #endif // __CC_SCRIPTOBJECT_H diff --git a/Engine/ac/dynobj/cc_serializer.cpp b/Engine/ac/dynobj/cc_serializer.cpp index 43d215b00d1..7e4f668f0a9 100644 --- a/Engine/ac/dynobj/cc_serializer.cpp +++ b/Engine/ac/dynobj/cc_serializer.cpp @@ -98,8 +98,7 @@ void AGSDeSerializer::Unserialize(int index, const char *objectType, const char ccDynamicObject.Unserialize(index, &mems, data_sz); } else if (strcmp(objectType, "String") == 0) { - ScriptString *scf = new ScriptString(); - scf->Unserialize(index, &mems, data_sz); + myScriptStringImpl.Unserialize(index, &mems, data_sz); } else if (strcmp(objectType, "File") == 0) { // files cannot be restored properly -- so just recreate diff --git a/Engine/ac/dynobj/dynobj_manager.cpp b/Engine/ac/dynobj/dynobj_manager.cpp index 546e216a979..ec91122d30c 100644 --- a/Engine/ac/dynobj/dynobj_manager.cpp +++ b/Engine/ac/dynobj/dynobj_manager.cpp @@ -21,13 +21,6 @@ using namespace AGS::Common; -ICCStringClass *stringClassImpl = nullptr; - -// set the class that will be used for dynamic strings -void ccSetStringClassImpl(ICCStringClass *theClass) { - stringClassImpl = theClass; -} - // register a memory handle for the object and allow script // pointers to point to it int32_t ccRegisterManagedObject(void *object, IScriptObject *callback, ScriptValueType obj_type) { diff --git a/Engine/ac/dynobj/dynobj_manager.h b/Engine/ac/dynobj/dynobj_manager.h index 133228bf315..bc117c3a1b5 100644 --- a/Engine/ac/dynobj/dynobj_manager.h +++ b/Engine/ac/dynobj/dynobj_manager.h @@ -28,8 +28,6 @@ namespace AGS { namespace Common { class Stream; } } using namespace AGS; // FIXME later -// set the class that will be used for dynamic strings -extern void ccSetStringClassImpl(ICCStringClass *theClass); // register a memory handle for the object and allow script // pointers to point to it extern int32_t ccRegisterManagedObject(void *object, IScriptObject *, ScriptValueType obj_type = kScValScriptObject); @@ -53,6 +51,4 @@ extern ScriptValueType ccGetObjectAddressAndManagerFromHandle(int32_t handle, vo extern int ccAddObjectReference(int32_t handle); extern int ccReleaseObjectReference(int32_t handle); -extern ICCStringClass *stringClassImpl; - #endif // __AGS_EE_DYNOBJ__DYNOBJMANAGER_H diff --git a/Engine/ac/dynobj/scriptstring.cpp b/Engine/ac/dynobj/scriptstring.cpp index 4e4c9a70a4e..715324da1ee 100644 --- a/Engine/ac/dynobj/scriptstring.cpp +++ b/Engine/ac/dynobj/scriptstring.cpp @@ -20,59 +20,58 @@ using namespace AGS::Common; +ScriptString myScriptStringImpl; -DynObjectRef ScriptString::CreateString(const char *fromText) { - return CreateNewScriptStringObj(fromText); -} - -int ScriptString::Dispose(void* /*address*/, bool /*force*/) { - // always dispose - if (_text) { - free(_text); - _text = nullptr; - } - delete this; - return 1; -} - -const char *ScriptString::GetType() { +const char *ScriptString::GetType() +{ return "String"; } -size_t ScriptString::CalcSerializeSize(const void* /*address*/) +int ScriptString::Dispose(void *address, bool /*force*/) { - return _len + 1 + sizeof(int32_t); + delete[] (static_cast(address) - MemHeaderSz); + return 1; } -void ScriptString::Serialize(const void* /*address*/, Stream *out) { - const auto *cstr = _text ? _text : ""; - out->WriteInt32(_len); - out->Write(cstr, _len + 1); +size_t ScriptString::CalcSerializeSize(const void *address) +{ + const Header &hdr = GetHeader(address); + return hdr.Length + 1 + FileHeaderSz; } -void ScriptString::Unserialize(int index, Stream *in, size_t /*data_sz*/) { - _len = in->ReadInt32(); - _text = (char*)malloc(_len + 1); - in->Read(_text, _len + 1); - _text[_len] = 0; // for safety - ccRegisterUnserializedObject(index, _text, this); +void ScriptString::Serialize(const void* address, Stream *out) +{ + const Header &hdr = GetHeader(address); + out->WriteInt32(hdr.Length); + out->Write(address, hdr.Length + 1); // it was writing trailing 0 for some reason } -ScriptString::ScriptString(const char *text) { - _len = strlen(text); - _text = (char*)malloc(_len + 1); - memcpy(_text, text, _len + 1); +void ScriptString::Unserialize(int index, Stream *in, size_t /*data_sz*/) +{ + size_t len = in->ReadInt32(); + uint8_t *buf = new uint8_t[len + 1 + MemHeaderSz]; + Header &hdr = reinterpret_cast(*buf); + hdr.Length = len; + char *text_ptr = reinterpret_cast(buf + MemHeaderSz); + in->Read(text_ptr, len + 1); // it was writing trailing 0 for some reason + text_ptr[len] = 0; // for safety + ccRegisterUnserializedObject(index, text_ptr, this); } -ScriptString::ScriptString(char *text, bool take_ownership) { - _len = strlen(text); - if (take_ownership) - { - _text = text; - } - else +DynObjectRef ScriptString::CreateImpl(const char *text, size_t buf_len) +{ + size_t len = text ? strlen(text) : buf_len; + uint8_t *buf = new uint8_t[len + 1 + MemHeaderSz]; + Header &hdr = reinterpret_cast(*buf); + hdr.Length = len; + char *text_ptr = reinterpret_cast(buf + MemHeaderSz); + if (text) + memcpy(text_ptr, text, len + 1); + int32_t handle = ccRegisterManagedObject(text_ptr, &myScriptStringImpl); + if (handle == 0) { - _text = (char*)malloc(_len + 1); - memcpy(_text, text, _len + 1); + delete[] buf; + return DynObjectRef(); } + return DynObjectRef(handle, text_ptr, &myScriptStringImpl); } diff --git a/Engine/ac/dynobj/scriptstring.h b/Engine/ac/dynobj/scriptstring.h index 12bcdc1e1b6..bd9feab4a5f 100644 --- a/Engine/ac/dynobj/scriptstring.h +++ b/Engine/ac/dynobj/scriptstring.h @@ -16,29 +16,46 @@ #include "ac/dynobj/cc_agsdynamicobject.h" -struct ScriptString final : AGSCCDynamicObject, ICCStringClass { - int Dispose(void *address, bool force) override; +struct ScriptString final : AGSCCDynamicObject +{ +public: + struct Header + { + uint32_t Length = 0u; + }; + + ScriptString() = default; + ~ScriptString() = default; + + inline static const Header &GetHeader(const void *address) + { + return reinterpret_cast(*(static_cast(address) - MemHeaderSz)); + } + + // Create a new script string by copying the given text + static DynObjectRef Create(const char *text) { return CreateImpl(text, -1); } + // Create a new script string with a buffer of at least the given text length + static DynObjectRef Create(size_t buf_len) { return CreateImpl(nullptr, buf_len); } + const char *GetType() override; + int Dispose(void *address, bool force) override; void Unserialize(int index, AGS::Common::Stream *in, size_t data_sz) override; - DynObjectRef CreateString(const char *fromText) override; +private: + // The size of the array's header in memory, prepended to the element data + static const size_t MemHeaderSz = sizeof(Header); + // The size of the serialized header + static const size_t FileHeaderSz = sizeof(uint32_t); - ScriptString() = default; - ScriptString(const char *text); - ScriptString(char *text, bool take_ownership); - char *GetTextPtr() const { return _text; } + static DynObjectRef CreateImpl(const char *text, size_t buf_len); -protected: + // Savegame serialization // Calculate and return required space for serialization, in bytes size_t CalcSerializeSize(const void *address) override; // Write object data into the provided stream void Serialize(const void *address, AGS::Common::Stream *out) override; - -private: - // TODO: the preallocated text buffer may be assigned externally; - // find out if it's possible to refactor while keeping same functionality - char *_text = nullptr; - size_t _len = 0; }; -#endif // __AC_SCRIPTSTRING_H \ No newline at end of file +extern ScriptString myScriptStringImpl; + +#endif // __AC_SCRIPTSTRING_H diff --git a/Engine/ac/file.cpp b/Engine/ac/file.cpp index 93cff2d2c7c..fc1bc97091c 100644 --- a/Engine/ac/file.cpp +++ b/Engine/ac/file.cpp @@ -173,10 +173,9 @@ const char* File_ReadStringBack(sc_File *fil) { return CreateNewScriptString("");; } - char *retVal = (char*)malloc(lle); - in->Read(retVal, lle); - - return CreateNewScriptString(retVal, false); + char *buffer = CreateNewScriptString(lle); + in->Read(buffer, lle); + return buffer; } int File_ReadInt(sc_File *fil) { @@ -726,7 +725,6 @@ Stream *get_valid_file_stream_from_handle(int32_t handle, const char *operation_ #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; // int (const char *fnmm) RuntimeScriptValue Sc_File_Delete(const RuntimeScriptValue *params, int32_t param_count) diff --git a/Engine/ac/game.cpp b/Engine/ac/game.cpp index b3502333866..866fe8f839b 100644 --- a/Engine/ac/game.cpp +++ b/Engine/ac/game.cpp @@ -128,7 +128,6 @@ CCObject ccDynamicObject; CCDialog ccDynamicDialog; CCAudioClip ccDynamicAudioClip; CCAudioChannel ccDynamicAudio; -ScriptString myScriptStringImpl; ScriptObject scrObj[MAX_ROOM_OBJECTS]; std::vector scrGui; ScriptHotspot scrHotspot[MAX_ROOM_HOTSPOTS]; diff --git a/Engine/ac/global_api.cpp b/Engine/ac/global_api.cpp index 6cf91259612..7d19604aa3f 100644 --- a/Engine/ac/global_api.cpp +++ b/Engine/ac/global_api.cpp @@ -70,7 +70,6 @@ #include "media/audio/audio_system.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; // void (char*texx, ...) RuntimeScriptValue Sc_sc_AbortGame(const RuntimeScriptValue *params, int32_t param_count) diff --git a/Engine/ac/gui.cpp b/Engine/ac/gui.cpp index da86223afeb..254b7fbef57 100644 --- a/Engine/ac/gui.cpp +++ b/Engine/ac/gui.cpp @@ -668,8 +668,6 @@ void gui_on_mouse_down(const int guin, const int mbut) #include "script/script_runtime.h" -extern ScriptString myScriptStringImpl; - ScriptGUI *GUI_GetByName(const char *name) { return static_cast(ccGetScriptObjectAddress(name, ccDynamicGUI.GetType())); diff --git a/Engine/ac/guicontrol.cpp b/Engine/ac/guicontrol.cpp index 27acf15b491..8e74637c47e 100644 --- a/Engine/ac/guicontrol.cpp +++ b/Engine/ac/guicontrol.cpp @@ -232,8 +232,6 @@ void GUIControl_SetTransparency(GUIObject *guio, int trans) { #include "script/script_runtime.h" -extern ScriptString myScriptStringImpl; - GUIObject *GUIControl_GetByName(const char *name) { return static_cast(ccGetScriptObjectAddress(name, ccDynamicGUIObject.GetType())); diff --git a/Engine/ac/hotspot.cpp b/Engine/ac/hotspot.cpp index 3756fb22082..0d6cafd46c7 100644 --- a/Engine/ac/hotspot.cpp +++ b/Engine/ac/hotspot.cpp @@ -143,8 +143,6 @@ int get_hotspot_at(int xpp,int ypp) { #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - ScriptHotspot *Hotspot_GetByName(const char *name) { diff --git a/Engine/ac/inventoryitem.cpp b/Engine/ac/inventoryitem.cpp index 4f87d94b708..0760f4ccacb 100644 --- a/Engine/ac/inventoryitem.cpp +++ b/Engine/ac/inventoryitem.cpp @@ -133,8 +133,6 @@ void set_inv_item_cursorpic(int invItemId, int piccy) #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - ScriptInvItem *InventoryItem_GetByName(const char *name) { diff --git a/Engine/ac/label.cpp b/Engine/ac/label.cpp index 04e5ca68bd7..c255b97d1c0 100644 --- a/Engine/ac/label.cpp +++ b/Engine/ac/label.cpp @@ -99,8 +99,6 @@ void Label_SetFont(GUILabel *guil, int fontnum) { #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - // void (GUILabel *labl, char *buffer) RuntimeScriptValue Sc_Label_GetText(void *self, const RuntimeScriptValue *params, int32_t param_count) { diff --git a/Engine/ac/listbox.cpp b/Engine/ac/listbox.cpp index f646ada8031..794f6072329 100644 --- a/Engine/ac/listbox.cpp +++ b/Engine/ac/listbox.cpp @@ -352,8 +352,6 @@ GUIListBox* is_valid_listbox (int guin, int objn) { #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - // int (GUIListBox *lbb, const char *text) RuntimeScriptValue Sc_ListBox_AddItem(void *self, const RuntimeScriptValue *params, int32_t param_count) { diff --git a/Engine/ac/object.cpp b/Engine/ac/object.cpp index be17eb0b946..1f0ef2463b6 100644 --- a/Engine/ac/object.cpp +++ b/Engine/ac/object.cpp @@ -781,8 +781,6 @@ bool CycleViewAnim(int view, uint16_t &o_loop, uint16_t &o_frame, bool forwards, #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - ScriptObject *Object_GetByName(const char *name) { diff --git a/Engine/ac/parser.cpp b/Engine/ac/parser.cpp index 9e3b3191c8c..10957083fa8 100644 --- a/Engine/ac/parser.cpp +++ b/Engine/ac/parser.cpp @@ -302,8 +302,6 @@ int parse_sentence (const char *src_text, int *numwords, short*wordarray, short* #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - // int (const char *wordToFind) RuntimeScriptValue Sc_Parser_FindWordID(const RuntimeScriptValue *params, int32_t param_count) { diff --git a/Engine/ac/properties.cpp b/Engine/ac/properties.cpp index 134c3de1158..4e9fd7294c4 100644 --- a/Engine/ac/properties.cpp +++ b/Engine/ac/properties.cpp @@ -23,7 +23,6 @@ using namespace AGS::Common; extern GameSetupStruct game; -extern ScriptString myScriptStringImpl; // begin custom property functions diff --git a/Engine/ac/room.cpp b/Engine/ac/room.cpp index 59034a0f994..81e43dfdd11 100644 --- a/Engine/ac/room.cpp +++ b/Engine/ac/room.cpp @@ -1105,8 +1105,6 @@ void convert_move_path_to_room_resolution(MoveList *ml) #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - // ScriptDrawingSurface* (int backgroundNumber) RuntimeScriptValue Sc_Room_GetDrawingSurfaceForBackground(const RuntimeScriptValue *params, int32_t param_count) { diff --git a/Engine/ac/scriptcontainers.cpp b/Engine/ac/scriptcontainers.cpp index 8a3cabf96d1..c2302ac4d9e 100644 --- a/Engine/ac/scriptcontainers.cpp +++ b/Engine/ac/scriptcontainers.cpp @@ -27,8 +27,6 @@ #include "script/script_runtime.h" #include "util/bbop.h" -extern ScriptString myScriptStringImpl; - //============================================================================= // // Dictionary of strings script API. diff --git a/Engine/ac/string.cpp b/Engine/ac/string.cpp index 9dc5c316d7d..0ce1762358a 100644 --- a/Engine/ac/string.cpp +++ b/Engine/ac/string.cpp @@ -32,7 +32,17 @@ using namespace AGS::Common; extern GameSetupStruct game; extern GameState play; extern int longestline; -extern ScriptString myScriptStringImpl; + +const char *CreateNewScriptString(const char *text) +{ + return (const char*)ScriptString::Create(text).Obj; +} + +char *CreateNewScriptString(size_t buf_length) +{ + return (char*)ScriptString::Create(buf_length).Obj; +} + int String_IsNullOrEmpty(const char *thisString) { @@ -47,18 +57,20 @@ const char* String_Copy(const char *srcString) { } const char* String_Append(const char *thisString, const char *extrabit) { - char *buffer = (char*)malloc(strlen(thisString) + strlen(extrabit) + 1); + size_t new_len = strlen(thisString) + strlen(extrabit); + char *buffer = CreateNewScriptString(new_len); strcpy(buffer, thisString); strcat(buffer, extrabit); - return CreateNewScriptString(buffer, false); + return buffer; } const char* String_AppendChar(const char *thisString, int extraOne) { char chr[5]{}; size_t chw = usetc(chr, extraOne); - char *buffer = (char*)malloc(strlen(thisString) + chw + 1); + size_t new_len = strlen(thisString) + chw; + char *buffer = CreateNewScriptString(new_len); sprintf(buffer, "%s%s", thisString, chr); - return CreateNewScriptString(buffer, false); + return buffer; } const char* String_ReplaceCharAt(const char *thisString, int index, int newChar) { @@ -72,12 +84,12 @@ const char* String_ReplaceCharAt(const char *thisString, int index, int newChar) size_t old_sz = ucwidth(uchar); char new_chr[5]{}; size_t new_chw = usetc(new_chr, newChar); - size_t total_sz = off + remain_sz + new_chw - old_sz + 1; - char *buffer = (char*)malloc(total_sz); + size_t new_len = off + remain_sz + new_chw - old_sz; + char *buffer = CreateNewScriptString(new_len); memcpy(buffer, thisString, off); memcpy(buffer + off, new_chr, new_chw); memcpy(buffer + off + new_chw, thisString + off + old_sz, remain_sz - old_sz + 1); - return CreateNewScriptString(buffer, false); + return buffer; } const char* String_Truncate(const char *thisString, int length) { @@ -88,10 +100,10 @@ const char* String_Truncate(const char *thisString, int length) { return thisString; size_t sz = uoffset(thisString, length); - char *buffer = (char*)malloc(sz + 1); + char *buffer = CreateNewScriptString(sz); memcpy(buffer, thisString, sz); buffer[sz] = 0; - return CreateNewScriptString(buffer, false); + return buffer; } const char* String_Substring(const char *thisString, int index, int length) { @@ -105,10 +117,10 @@ const char* String_Substring(const char *thisString, int index, int length) { size_t end = uoffset(thisString + start, sublen) + start; size_t copysz = end - start; - char *buffer = (char*)malloc(copysz + 1); + char *buffer = CreateNewScriptString(copysz); memcpy(buffer, thisString + start, copysz); buffer[copysz] = 0; - return CreateNewScriptString(buffer, false); + return buffer; } int String_CompareTo(const char *thisString, const char *otherString, bool caseSensitive) { @@ -193,19 +205,23 @@ const char* String_Replace(const char *thisString, const char *lookForText, cons } resultBuffer[outputSize] = 0; // terminate - return CreateNewScriptString(resultBuffer, true); + return CreateNewScriptString(resultBuffer); } const char* String_LowerCase(const char *thisString) { - char *buffer = ags_strdup(thisString); + size_t len = strlen(thisString); + char *buffer = CreateNewScriptString(len); + memcpy(buffer, thisString, len); ustrlwr(buffer); - return CreateNewScriptString(buffer, false); + return buffer; } const char* String_UpperCase(const char *thisString) { - char *buffer = ags_strdup(thisString); + size_t len = strlen(thisString); + char *buffer = CreateNewScriptString(len); + memcpy(buffer, thisString, len); ustrupr(buffer); - return CreateNewScriptString(buffer, false); + return buffer; } int String_GetChars(const char *texx, int index) { @@ -244,37 +260,6 @@ int StrContains (const char *s1, const char *s2) { //============================================================================= -const char *CreateNewScriptString(const String &fromText) { - return (const char*)CreateNewScriptStringObj(fromText.GetCStr(), true).Obj; -} - -const char *CreateNewScriptString(const char *fromText, bool reAllocate) { - return (const char*)CreateNewScriptStringObj(fromText, reAllocate).Obj; -} - -DynObjectRef CreateNewScriptStringObj(const String &fromText) { - return CreateNewScriptStringObj(fromText.GetCStr(), true); -} - -DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate) -{ - ScriptString *str; - if (reAllocate) { - str = new ScriptString(fromText); - } - else { // TODO: refactor to avoid const casts! - str = new ScriptString((char*)fromText, true); - } - void *obj_ptr = str->GetTextPtr(); - int32_t handle = ccRegisterManagedObject(obj_ptr, str); - if (handle == 0) - { - delete str; - return DynObjectRef(); - } - return DynObjectRef(handle, obj_ptr, str); -} - size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLines &lines, int wii, int fonnt, size_t max_lines) { if (fonnt == -1) fonnt = play.normal_font; diff --git a/Engine/ac/string.h b/Engine/ac/string.h index 3d5d114b308..ebd1d4c1f07 100644 --- a/Engine/ac/string.h +++ b/Engine/ac/string.h @@ -30,6 +30,10 @@ inline void VALIDATE_STRING(const char *strin) quit("!String argument was null: make sure you pass a valid string as a buffer."); } +const char* CreateNewScriptString(const char *text); +inline const char* CreateNewScriptString(const AGS::Common::String &text) { return CreateNewScriptString(text.GetCStr()); } +char* CreateNewScriptString(size_t buf_len); // FIXME, unsafe to expose raw buf like this + int String_IsNullOrEmpty(const char *thisString); const char* String_Copy(const char *srcString); const char* String_Append(const char *thisString, const char *extrabit); @@ -49,10 +53,6 @@ int StrContains (const char *s1, const char *s2); //============================================================================= -const char* CreateNewScriptString(const AGS::Common::String &fromText); -const char* CreateNewScriptString(const char *fromText, bool reAllocate = true); -DynObjectRef CreateNewScriptStringObj(const AGS::Common::String &fromText); -DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate = true); class SplitLines; // Break up the text into lines restricted by the given width; // returns number of lines, or 0 if text cannot be split well to fit in this width. diff --git a/Engine/ac/system.cpp b/Engine/ac/system.cpp index be6497a160e..bd34eacc741 100644 --- a/Engine/ac/system.cpp +++ b/Engine/ac/system.cpp @@ -228,8 +228,6 @@ void System_SetRenderAtScreenResolution(int enable) #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - // int () RuntimeScriptValue Sc_System_GetAudioChannelCount(const RuntimeScriptValue *params, int32_t param_count) { diff --git a/Engine/ac/textbox.cpp b/Engine/ac/textbox.cpp index 88b97cf45ec..e0dc290630d 100644 --- a/Engine/ac/textbox.cpp +++ b/Engine/ac/textbox.cpp @@ -89,8 +89,6 @@ void TextBox_SetShowBorder(GUITextBox *guit, bool on) #include "script/script_runtime.h" #include "ac/dynobj/scriptstring.h" -extern ScriptString myScriptStringImpl; - // void (GUITextBox *texbox, char *buffer) RuntimeScriptValue Sc_TextBox_GetText(void *self, const RuntimeScriptValue *params, int32_t param_count) { diff --git a/Engine/game/game_init.cpp b/Engine/game/game_init.cpp index 4886f740a49..acb49652ac8 100644 --- a/Engine/game/game_init.cpp +++ b/Engine/game/game_init.cpp @@ -65,7 +65,6 @@ extern CCObject ccDynamicObject; extern CCDialog ccDynamicDialog; extern CCAudioChannel ccDynamicAudio; extern CCAudioClip ccDynamicAudioClip; -extern ScriptString myScriptStringImpl; extern ScriptObject scrObj[MAX_ROOM_OBJECTS]; extern std::vector scrGui; extern ScriptHotspot scrHotspot[MAX_ROOM_HOTSPOTS]; @@ -559,7 +558,6 @@ HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion dat // require access to script API at initialization time. // ccSetScriptAliveTimer(1000 / 60u, 1000u, 150000u); - ccSetStringClassImpl(&myScriptStringImpl); setup_script_exports(base_api, compat_api); // diff --git a/Engine/plugin/agsplugin.cpp b/Engine/plugin/agsplugin.cpp index 210eab43ecc..a824e11111b 100644 --- a/Engine/plugin/agsplugin.cpp +++ b/Engine/plugin/agsplugin.cpp @@ -91,7 +91,6 @@ extern RGB palette[256]; extern int displayed_room; extern RoomStruct thisroom; extern RoomStatus *croom; -extern ScriptString myScriptStringImpl; extern RuntimeScriptValue GlobalReturnValue; diff --git a/Engine/script/cc_instance.cpp b/Engine/script/cc_instance.cpp index 2e13438a9c0..4295ff16d3b 100644 --- a/Engine/script/cc_instance.cpp +++ b/Engine/script/cc_instance.cpp @@ -151,7 +151,6 @@ const char *fixupnames[] = { "null", "fix_gldata", "fix_func", "fix_string", "fi extern new_line_hook_type new_line_hook; -extern ScriptString myScriptStringImpl; ccInstance *loadedInstances[MAX_LOADED_INSTANCES] = { nullptr }; @@ -1588,20 +1587,9 @@ int ccInstance::Run(int32_t curpc) case SCMD_CREATESTRING: { auto ®1 = registers[codeOp.Arg1i()]; - // FIXME: provide a dummy impl to avoid this? - // why arrays can be created using global mgr and strings not? - if (stringClassImpl == nullptr) - { - cc_error("No string class implementation set, but opcode was used"); - return -1; - } - else - { - const char *ptr = reinterpret_cast(reg1.GetDirectPtr()); - reg1.SetScriptObject( - stringClassImpl->CreateString(ptr).Obj, - &myScriptStringImpl); - } + const char *ptr = reinterpret_cast(reg1.GetDirectPtr()); + DynObjectRef ref = ScriptString::Create(ptr); + reg1.SetScriptObject(ref.Obj, &myScriptStringImpl); break; } case SCMD_STRINGSEQUAL: From e4b3a4f5befb8f8a00287fe1451763d7fd05c1c9 Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Wed, 18 Oct 2023 00:58:35 +0300 Subject: [PATCH 2/6] Engine: write ScriptStrings by using a std::moveable Buffer Instead of giving out a pointer to ScriptString internals, have a dedicated ScriptString::Buffer struct that already has a buffer allocated according to ScriptString's rules (with meta header). ScriptString provides a method for creating such buffer, and for creating a new ScriptString object from the Buffer. This has an advantage of one less memory copy compared to passing a const char* into a factory method, which may make some difference when modifying very long strings. --- Engine/ac/dynobj/cc_scriptobject.h | 1 + Engine/ac/dynobj/scriptstring.cpp | 34 +++++++++--- Engine/ac/dynobj/scriptstring.h | 31 +++++++++-- Engine/ac/file.cpp | 10 ++-- Engine/ac/string.cpp | 89 +++++++++++++----------------- Engine/ac/string.h | 12 ++-- 6 files changed, 106 insertions(+), 71 deletions(-) diff --git a/Engine/ac/dynobj/cc_scriptobject.h b/Engine/ac/dynobj/cc_scriptobject.h index 8c32149c69b..7254c298afd 100644 --- a/Engine/ac/dynobj/cc_scriptobject.h +++ b/Engine/ac/dynobj/cc_scriptobject.h @@ -38,6 +38,7 @@ struct DynObjectRef DynObjectRef() = default; DynObjectRef(int handle, void *obj, IScriptObject *mgr) : Handle(handle), Obj(obj), Mgr(mgr) {} + inline operator bool() const { return Handle > 0; } }; diff --git a/Engine/ac/dynobj/scriptstring.cpp b/Engine/ac/dynobj/scriptstring.cpp index 715324da1ee..10ae7003405 100644 --- a/Engine/ac/dynobj/scriptstring.cpp +++ b/Engine/ac/dynobj/scriptstring.cpp @@ -58,15 +58,9 @@ void ScriptString::Unserialize(int index, Stream *in, size_t /*data_sz*/) ccRegisterUnserializedObject(index, text_ptr, this); } -DynObjectRef ScriptString::CreateImpl(const char *text, size_t buf_len) +DynObjectRef ScriptString::CreateObject(uint8_t *buf) { - size_t len = text ? strlen(text) : buf_len; - uint8_t *buf = new uint8_t[len + 1 + MemHeaderSz]; - Header &hdr = reinterpret_cast(*buf); - hdr.Length = len; char *text_ptr = reinterpret_cast(buf + MemHeaderSz); - if (text) - memcpy(text_ptr, text, len + 1); int32_t handle = ccRegisterManagedObject(text_ptr, &myScriptStringImpl); if (handle == 0) { @@ -75,3 +69,29 @@ DynObjectRef ScriptString::CreateImpl(const char *text, size_t buf_len) } return DynObjectRef(handle, text_ptr, &myScriptStringImpl); } + +ScriptString::Buffer ScriptString::CreateBuffer(size_t data_sz) +{ + std::unique_ptr buf(new uint8_t[data_sz + MemHeaderSz]); + return Buffer(std::move(buf), data_sz + MemHeaderSz); +} + +DynObjectRef ScriptString::Create(const char *text) +{ + size_t len = strlen(text); + uint8_t *buf = new uint8_t[len + 1 + MemHeaderSz]; + char *text_ptr = reinterpret_cast(buf + MemHeaderSz); + memcpy(buf, text, len + 1); + Header &hdr = reinterpret_cast(*buf); + hdr.Length = len; + return CreateObject(buf); +} + +DynObjectRef ScriptString::Create(Buffer &&strbuf) +{ + uint8_t *buf = strbuf._buf.release(); + Header &hdr = reinterpret_cast(*buf); + hdr.Length = strbuf._sz - 1; + strbuf._sz = 0u; + return CreateObject(buf); +} diff --git a/Engine/ac/dynobj/scriptstring.h b/Engine/ac/dynobj/scriptstring.h index bd9feab4a5f..8fdb008ed65 100644 --- a/Engine/ac/dynobj/scriptstring.h +++ b/Engine/ac/dynobj/scriptstring.h @@ -14,6 +14,7 @@ #ifndef __AC_SCRIPTSTRING_H #define __AC_SCRIPTSTRING_H +#include #include "ac/dynobj/cc_agsdynamicobject.h" struct ScriptString final : AGSCCDynamicObject @@ -24,6 +25,25 @@ struct ScriptString final : AGSCCDynamicObject uint32_t Length = 0u; }; + struct Buffer + { + friend ScriptString; + public: + Buffer() = default; + ~Buffer() = default; + Buffer(Buffer &&buf) = default; + char *Get() { return reinterpret_cast(_buf.get() - MemHeaderSz); } + size_t GetSize() const { return _sz; } + + private: + Buffer(std::unique_ptr &&buf, size_t buf_sz) + : _buf(std::move(buf)), _sz(buf_sz) {} + + std::unique_ptr _buf; + size_t _sz; + }; + + ScriptString() = default; ~ScriptString() = default; @@ -32,22 +52,25 @@ struct ScriptString final : AGSCCDynamicObject return reinterpret_cast(*(static_cast(address) - MemHeaderSz)); } + // + static Buffer CreateBuffer(size_t data_sz); // Create a new script string by copying the given text - static DynObjectRef Create(const char *text) { return CreateImpl(text, -1); } - // Create a new script string with a buffer of at least the given text length - static DynObjectRef Create(size_t buf_len) { return CreateImpl(nullptr, buf_len); } + static DynObjectRef Create(const char *text); + // + static DynObjectRef Create(Buffer &&strbuf); const char *GetType() override; int Dispose(void *address, bool force) override; void Unserialize(int index, AGS::Common::Stream *in, size_t data_sz) override; private: + friend ScriptString::Buffer; // The size of the array's header in memory, prepended to the element data static const size_t MemHeaderSz = sizeof(Header); // The size of the serialized header static const size_t FileHeaderSz = sizeof(uint32_t); - static DynObjectRef CreateImpl(const char *text, size_t buf_len); + static DynObjectRef CreateObject(uint8_t *buf); // Savegame serialization // Calculate and return required space for serialization, in bytes diff --git a/Engine/ac/file.cpp b/Engine/ac/file.cpp index fc1bc97091c..e6faf0e2618 100644 --- a/Engine/ac/file.cpp +++ b/Engine/ac/file.cpp @@ -166,16 +166,16 @@ const char* File_ReadStringBack(sc_File *fil) { return CreateNewScriptString(""); } - size_t lle = (uint32_t)in->ReadInt32(); - if (lle == 0) + size_t data_sz = (uint32_t)in->ReadInt32(); + if (data_sz == 0) { debug_script_warn("File.ReadStringBack: file was not written by WriteString"); return CreateNewScriptString("");; } - char *buffer = CreateNewScriptString(lle); - in->Read(buffer, lle); - return buffer; + auto buf = ScriptString::CreateBuffer(data_sz); // stored len + 1 + in->Read(buf.Get(), data_sz); + return CreateNewScriptString(std::move(buf)); } int File_ReadInt(sc_File *fil) { diff --git a/Engine/ac/string.cpp b/Engine/ac/string.cpp index 0ce1762358a..84817b823f8 100644 --- a/Engine/ac/string.cpp +++ b/Engine/ac/string.cpp @@ -35,12 +35,7 @@ extern int longestline; const char *CreateNewScriptString(const char *text) { - return (const char*)ScriptString::Create(text).Obj; -} - -char *CreateNewScriptString(size_t buf_length) -{ - return (char*)ScriptString::Create(buf_length).Obj; + return static_cast(ScriptString::Create(text).Obj); } @@ -57,20 +52,22 @@ const char* String_Copy(const char *srcString) { } const char* String_Append(const char *thisString, const char *extrabit) { - size_t new_len = strlen(thisString) + strlen(extrabit); - char *buffer = CreateNewScriptString(new_len); - strcpy(buffer, thisString); - strcat(buffer, extrabit); - return buffer; + size_t old_len = strlen(thisString); + size_t str2_len = strlen(extrabit); + auto buf = ScriptString::CreateBuffer(old_len + str2_len + 1); + memcpy(buf.Get(), thisString, old_len); + memcpy(buf.Get() + old_len, extrabit, str2_len + 1); + return CreateNewScriptString(std::move(buf)); } const char* String_AppendChar(const char *thisString, int extraOne) { char chr[5]{}; - size_t chw = usetc(chr, extraOne); - size_t new_len = strlen(thisString) + chw; - char *buffer = CreateNewScriptString(new_len); - sprintf(buffer, "%s%s", thisString, chr); - return buffer; + size_t old_len = strlen(thisString); + size_t new_chw = usetc(chr, extraOne); + auto buf = ScriptString::CreateBuffer(old_len + new_chw + 1); + memcpy(buf.Get(), thisString, old_len); + memcpy(buf.Get() + old_len, chr, new_chw + 1); + return CreateNewScriptString(std::move(buf)); } const char* String_ReplaceCharAt(const char *thisString, int index, int newChar) { @@ -79,17 +76,17 @@ const char* String_ReplaceCharAt(const char *thisString, int index, int newChar) quit("!String.ReplaceCharAt: index outside range of string"); size_t off = uoffset(thisString, index); - int uchar = ugetc(thisString + off); + int old_char = ugetc(thisString + off); size_t remain_sz = strlen(thisString + off); - size_t old_sz = ucwidth(uchar); + size_t old_chw = ucwidth(old_char); char new_chr[5]{}; size_t new_chw = usetc(new_chr, newChar); - size_t new_len = off + remain_sz + new_chw - old_sz; - char *buffer = CreateNewScriptString(new_len); - memcpy(buffer, thisString, off); - memcpy(buffer + off, new_chr, new_chw); - memcpy(buffer + off + new_chw, thisString + off + old_sz, remain_sz - old_sz + 1); - return buffer; + size_t new_len = off + remain_sz + new_chw - old_chw; + auto buf = ScriptString::CreateBuffer(new_len + 1); + memcpy(buf.Get(), thisString, off); + memcpy(buf.Get() + off, new_chr, new_chw); + memcpy(buf.Get() + off + new_chw, thisString + off + old_chw, remain_sz - old_chw + 1); + return CreateNewScriptString(std::move(buf)); } const char* String_Truncate(const char *thisString, int length) { @@ -99,11 +96,11 @@ const char* String_Truncate(const char *thisString, int length) { if ((size_t)length >= strlen) return thisString; - size_t sz = uoffset(thisString, length); - char *buffer = CreateNewScriptString(sz); - memcpy(buffer, thisString, sz); - buffer[sz] = 0; - return buffer; + size_t new_len = uoffset(thisString, length); + auto buf = ScriptString::CreateBuffer(new_len + 1); + memcpy(buf.Get(), thisString, new_len); + buf.Get()[new_len] = 0; + return CreateNewScriptString(std::move(buf)); } const char* String_Substring(const char *thisString, int index, int length) { @@ -115,12 +112,12 @@ const char* String_Substring(const char *thisString, int index, int length) { size_t sublen = std::min((size_t)length, strlen - index); size_t start = uoffset(thisString, index); size_t end = uoffset(thisString + start, sublen) + start; - size_t copysz = end - start; + size_t copylen = end - start; - char *buffer = CreateNewScriptString(copysz); - memcpy(buffer, thisString + start, copysz); - buffer[copysz] = 0; - return buffer; + auto buf = ScriptString::CreateBuffer(copylen + 1); + memcpy(buf.Get(), thisString + start, copylen); + buf.Get()[copylen] = 0; + return CreateNewScriptString(std::move(buf)); } int String_CompareTo(const char *thisString, const char *otherString, bool caseSensitive) { @@ -210,18 +207,18 @@ const char* String_Replace(const char *thisString, const char *lookForText, cons const char* String_LowerCase(const char *thisString) { size_t len = strlen(thisString); - char *buffer = CreateNewScriptString(len); - memcpy(buffer, thisString, len); - ustrlwr(buffer); - return buffer; + auto buf = ScriptString::CreateBuffer(len + 1); + memcpy(buf.Get(), thisString, len + 1); + ustrlwr(buf.Get()); + return CreateNewScriptString(std::move(buf)); } const char* String_UpperCase(const char *thisString) { size_t len = strlen(thisString); - char *buffer = CreateNewScriptString(len); - memcpy(buffer, thisString, len); - ustrupr(buffer); - return buffer; + auto buf = ScriptString::CreateBuffer(len + 1); + memcpy(buf.Get(), thisString, len + 1); + ustrupr(buf.Get()); + return CreateNewScriptString(std::move(buf)); } int String_GetChars(const char *texx, int index) { @@ -313,14 +310,6 @@ void check_strlen(char*ptt) { MAXSTRLEN=30; } -/*void GetLanguageString(int indxx,char*buffr) { -VALIDATE_STRING(buffr); -char*bptr=get_language_text(indxx); -if (bptr==NULL) strcpy(buffr,"[language string error]"); -else strncpy(buffr,bptr,199); -buffr[199]=0; -}*/ - void my_strncpy(char *dest, const char *src, int len) { // the normal strncpy pads out the string with zeros up to the // max length -- we don't want that diff --git a/Engine/ac/string.h b/Engine/ac/string.h index ebd1d4c1f07..f28782e048d 100644 --- a/Engine/ac/string.h +++ b/Engine/ac/string.h @@ -12,7 +12,7 @@ // //============================================================================= // -// +// Script String API implementation. // //============================================================================= #ifndef __AGS_EE_AC__STRING_H @@ -20,7 +20,7 @@ #include #include "ac/common.h" // quit -#include "ac/dynobj/cc_scriptobject.h" +#include "ac/dynobj/scriptstring.h" #include "util/string.h" // Check that a supplied buffer from a text script function was not null @@ -30,9 +30,11 @@ inline void VALIDATE_STRING(const char *strin) quit("!String argument was null: make sure you pass a valid string as a buffer."); } -const char* CreateNewScriptString(const char *text); -inline const char* CreateNewScriptString(const AGS::Common::String &text) { return CreateNewScriptString(text.GetCStr()); } -char* CreateNewScriptString(size_t buf_len); // FIXME, unsafe to expose raw buf like this +const char *CreateNewScriptString(const char *text); +inline const char *CreateNewScriptString(const AGS::Common::String &text) + { return CreateNewScriptString(text.GetCStr()); } +inline const char *CreateNewScriptString(ScriptString::Buffer &&buf) + { return static_cast(ScriptString::Create(std::move(buf)).Obj); } int String_IsNullOrEmpty(const char *thisString); const char* String_Copy(const char *srcString); From af1a76924e30346fc5b43ba4a141b9c56f14a44c Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Wed, 18 Oct 2023 02:40:12 +0300 Subject: [PATCH 3/6] Engine: ScriptString stores length in bytes and uchars, uses in API --- Engine/ac/dynobj/scriptstring.cpp | 24 ++++---- Engine/ac/dynobj/scriptstring.h | 11 ++-- Engine/ac/string.cpp | 75 +++++++++++++----------- libsrc/allegro/include/allegro/unicode.h | 1 + libsrc/allegro/src/unicode.c | 19 ++++++ 5 files changed, 81 insertions(+), 49 deletions(-) diff --git a/Engine/ac/dynobj/scriptstring.cpp b/Engine/ac/dynobj/scriptstring.cpp index 10ae7003405..2db5339085f 100644 --- a/Engine/ac/dynobj/scriptstring.cpp +++ b/Engine/ac/dynobj/scriptstring.cpp @@ -14,6 +14,7 @@ #include "ac/dynobj/scriptstring.h" #include #include +#include #include "ac/string.h" #include "ac/dynobj/dynobj_manager.h" #include "util/stream.h" @@ -50,16 +51,20 @@ void ScriptString::Unserialize(int index, Stream *in, size_t /*data_sz*/) { size_t len = in->ReadInt32(); uint8_t *buf = new uint8_t[len + 1 + MemHeaderSz]; - Header &hdr = reinterpret_cast(*buf); - hdr.Length = len; char *text_ptr = reinterpret_cast(buf + MemHeaderSz); in->Read(text_ptr, len + 1); // it was writing trailing 0 for some reason text_ptr[len] = 0; // for safety + Header &hdr = reinterpret_cast(*buf); + hdr.Length = len; + hdr.ULength = ustrlen(text_ptr); ccRegisterUnserializedObject(index, text_ptr, this); } -DynObjectRef ScriptString::CreateObject(uint8_t *buf) +DynObjectRef ScriptString::CreateObject(uint8_t *buf, size_t len, size_t ulen) { + Header &hdr = reinterpret_cast(*buf); + hdr.Length = len; + hdr.ULength = ulen; char *text_ptr = reinterpret_cast(buf + MemHeaderSz); int32_t handle = ccRegisterManagedObject(text_ptr, &myScriptStringImpl); if (handle == 0) @@ -78,20 +83,19 @@ ScriptString::Buffer ScriptString::CreateBuffer(size_t data_sz) DynObjectRef ScriptString::Create(const char *text) { - size_t len = strlen(text); + int len, ulen; + ustrlen2(text, &len, &ulen); uint8_t *buf = new uint8_t[len + 1 + MemHeaderSz]; char *text_ptr = reinterpret_cast(buf + MemHeaderSz); memcpy(buf, text, len + 1); - Header &hdr = reinterpret_cast(*buf); - hdr.Length = len; - return CreateObject(buf); + return CreateObject(buf, len, ulen); } DynObjectRef ScriptString::Create(Buffer &&strbuf) { + int len, ulen; + ustrlen2(strbuf.Get(), &len, &ulen); uint8_t *buf = strbuf._buf.release(); - Header &hdr = reinterpret_cast(*buf); - hdr.Length = strbuf._sz - 1; strbuf._sz = 0u; - return CreateObject(buf); + return CreateObject(buf, len, ulen); } diff --git a/Engine/ac/dynobj/scriptstring.h b/Engine/ac/dynobj/scriptstring.h index 8fdb008ed65..be26ec0efd1 100644 --- a/Engine/ac/dynobj/scriptstring.h +++ b/Engine/ac/dynobj/scriptstring.h @@ -22,7 +22,8 @@ struct ScriptString final : AGSCCDynamicObject public: struct Header { - uint32_t Length = 0u; + uint32_t Length = 0u; // string length in bytes (not counting 0) + uint32_t ULength = 0u; // UTF-8 compatible length in characters }; struct Buffer @@ -32,8 +33,10 @@ struct ScriptString final : AGSCCDynamicObject Buffer() = default; ~Buffer() = default; Buffer(Buffer &&buf) = default; - char *Get() { return reinterpret_cast(_buf.get() - MemHeaderSz); } - size_t GetSize() const { return _sz; } + // Returns a pointer to the beginning of a text buffer + char *Get() { return reinterpret_cast(_buf.get() + MemHeaderSz); } + // Returns size allocated for a text content (includes null pointer) + size_t GetSize() const { return _sz - MemHeaderSz; } private: Buffer(std::unique_ptr &&buf, size_t buf_sz) @@ -70,7 +73,7 @@ struct ScriptString final : AGSCCDynamicObject // The size of the serialized header static const size_t FileHeaderSz = sizeof(uint32_t); - static DynObjectRef CreateObject(uint8_t *buf); + static DynObjectRef CreateObject(uint8_t *buf, size_t len, size_t ulen); // Savegame serialization // Calculate and return required space for serialization, in bytes diff --git a/Engine/ac/string.cpp b/Engine/ac/string.cpp index 84817b823f8..c2c7db272cb 100644 --- a/Engine/ac/string.cpp +++ b/Engine/ac/string.cpp @@ -52,48 +52,47 @@ const char* String_Copy(const char *srcString) { } const char* String_Append(const char *thisString, const char *extrabit) { - size_t old_len = strlen(thisString); + const auto &header = ScriptString::GetHeader(thisString); size_t str2_len = strlen(extrabit); - auto buf = ScriptString::CreateBuffer(old_len + str2_len + 1); - memcpy(buf.Get(), thisString, old_len); - memcpy(buf.Get() + old_len, extrabit, str2_len + 1); + auto buf = ScriptString::CreateBuffer(header.Length + str2_len + 1); + memcpy(buf.Get(), thisString, header.Length); + memcpy(buf.Get() + header.Length, extrabit, str2_len + 1); return CreateNewScriptString(std::move(buf)); } const char* String_AppendChar(const char *thisString, int extraOne) { char chr[5]{}; - size_t old_len = strlen(thisString); + const auto &header = ScriptString::GetHeader(thisString); size_t new_chw = usetc(chr, extraOne); - auto buf = ScriptString::CreateBuffer(old_len + new_chw + 1); - memcpy(buf.Get(), thisString, old_len); - memcpy(buf.Get() + old_len, chr, new_chw + 1); + auto buf = ScriptString::CreateBuffer(header.Length + new_chw + 1); + memcpy(buf.Get(), thisString, header.Length); + memcpy(buf.Get() + header.Length, chr, new_chw + 1); return CreateNewScriptString(std::move(buf)); } const char* String_ReplaceCharAt(const char *thisString, int index, int newChar) { - size_t len = ustrlen(thisString); - if ((index < 0) || ((size_t)index >= len)) + const auto &header = ScriptString::GetHeader(thisString); + if ((index < 0) || ((size_t)index >= header.ULength)) quit("!String.ReplaceCharAt: index outside range of string"); size_t off = uoffset(thisString, index); int old_char = ugetc(thisString + off); - size_t remain_sz = strlen(thisString + off); size_t old_chw = ucwidth(old_char); char new_chr[5]{}; size_t new_chw = usetc(new_chr, newChar); - size_t new_len = off + remain_sz + new_chw - old_chw; + size_t new_len = header.Length + new_chw - old_chw; auto buf = ScriptString::CreateBuffer(new_len + 1); memcpy(buf.Get(), thisString, off); memcpy(buf.Get() + off, new_chr, new_chw); - memcpy(buf.Get() + off + new_chw, thisString + off + old_chw, remain_sz - old_chw + 1); + memcpy(buf.Get() + off + new_chw, thisString + off + old_chw, header.Length - off - old_chw + 1); return CreateNewScriptString(std::move(buf)); } const char* String_Truncate(const char *thisString, int length) { if (length < 0) quit("!String.Truncate: invalid length"); - size_t strlen = ustrlen(thisString); - if ((size_t)length >= strlen) + const auto &header = ScriptString::GetHeader(thisString); + if ((size_t)length >= header.ULength) return thisString; size_t new_len = uoffset(thisString, length); @@ -106,10 +105,10 @@ const char* String_Truncate(const char *thisString, int length) { const char* String_Substring(const char *thisString, int index, int length) { if (length < 0) quit("!String.Substring: invalid length"); - size_t strlen = ustrlen(thisString); - if ((index < 0) || ((size_t)index > strlen)) + const auto &header = ScriptString::GetHeader(thisString); + if ((index < 0) || ((size_t)index > header.ULength)) quit("!String.Substring: invalid index"); - size_t sublen = std::min((size_t)length, strlen - index); + size_t sublen = std::min(length, header.ULength - index); size_t start = uoffset(thisString, index); size_t end = uoffset(thisString + start, sublen) + start; size_t copylen = end - start; @@ -142,17 +141,18 @@ int String_StartsWith(const char *thisString, const char *checkForString, bool c int String_EndsWith(const char *thisString, const char *checkForString, bool caseSensitive) { // NOTE: we need size in bytes here - size_t thislen = strlen(thisString), checklen = strlen(checkForString); - if (checklen > thislen) + const auto &header = ScriptString::GetHeader(thisString); + size_t checklen = strlen(checkForString); + if (checklen > header.Length) return 0; if (caseSensitive) { - return (strcmp(thisString + (thislen - checklen), checkForString) == 0) ? 1 : 0; + return (strcmp(thisString + (header.Length - checklen), checkForString) == 0) ? 1 : 0; } else { - return (ustricmp(thisString + (thislen - checklen), checkForString) == 0) ? 1 : 0; + return (ustricmp(thisString + (header.Length - checklen), checkForString) == 0) ? 1 : 0; } } @@ -206,25 +206,26 @@ const char* String_Replace(const char *thisString, const char *lookForText, cons } const char* String_LowerCase(const char *thisString) { - size_t len = strlen(thisString); - auto buf = ScriptString::CreateBuffer(len + 1); - memcpy(buf.Get(), thisString, len + 1); + const auto &header = ScriptString::GetHeader(thisString); + auto buf = ScriptString::CreateBuffer(header.Length + 1); + memcpy(buf.Get(), thisString, header.Length + 1); ustrlwr(buf.Get()); return CreateNewScriptString(std::move(buf)); } const char* String_UpperCase(const char *thisString) { - size_t len = strlen(thisString); - auto buf = ScriptString::CreateBuffer(len + 1); - memcpy(buf.Get(), thisString, len + 1); + const auto &header = ScriptString::GetHeader(thisString); + auto buf = ScriptString::CreateBuffer(header.Length + 1); + memcpy(buf.Get(), thisString, header.Length + 1); ustrupr(buf.Get()); return CreateNewScriptString(std::move(buf)); } -int String_GetChars(const char *texx, int index) { - if ((index < 0) || (index >= ustrlen(texx))) +int String_GetChars(const char *thisString, int index) { + const auto &header = ScriptString::GetHeader(thisString); + if ((index < 0) || (static_cast(index) >= header.ULength)) return 0; - return ugetat(texx, index); + return ugetat(thisString, index); } int StringToInt(const char*stino) { @@ -255,6 +256,10 @@ int StrContains (const char *s1, const char *s2) { return at; } +int String_GetLength(const char *thisString) { + return ScriptString::GetHeader(thisString).ULength; +} + //============================================================================= size_t break_up_text_into_lines(const char *todis, bool apply_direction, SplitLines &lines, int wii, int fonnt, size_t max_lines) { @@ -441,10 +446,10 @@ RuntimeScriptValue Sc_String_GetChars(void *self, const RuntimeScriptValue *para API_OBJCALL_INT_PINT(const char, String_GetChars); } -RuntimeScriptValue Sc_strlen(void *self, const RuntimeScriptValue *params, int32_t param_count) +RuntimeScriptValue Sc_String_GetLength(void *self, const RuntimeScriptValue *params, int32_t param_count) { - ASSERT_SELF(strlen); - return RuntimeScriptValue().SetInt32(ustrlen((const char*)self)); + ASSERT_SELF(String_GetLength); + return RuntimeScriptValue().SetInt32(String_GetLength((const char*)self)); } //============================================================================= @@ -484,7 +489,7 @@ void RegisterStringAPI() { "String::get_AsFloat", API_FN_PAIR(StringToFloat) }, { "String::get_AsInt", API_FN_PAIR(StringToInt) }, { "String::geti_Chars", API_FN_PAIR(String_GetChars) }, - { "String::get_Length", API_FN_PAIR(strlen) }, + { "String::get_Length", API_FN_PAIR(String_GetLength) }, }; ccAddExternalFunctions(string_api); diff --git a/libsrc/allegro/include/allegro/unicode.h b/libsrc/allegro/include/allegro/unicode.h index efadd374de4..deef33f7c43 100644 --- a/libsrc/allegro/include/allegro/unicode.h +++ b/libsrc/allegro/include/allegro/unicode.h @@ -71,6 +71,7 @@ AL_FUNC(char *, _ustrdup, (AL_CONST char *src, AL_METHOD(void *, malloc_func, (s AL_FUNC(char *, ustrzcpy, (char *dest, int size, AL_CONST char *src)); AL_FUNC(char *, ustrzcat, (char *dest, int size, AL_CONST char *src)); AL_FUNC(int, ustrlen, (AL_CONST char *s)); +AL_FUNC(void, ustrlen2, (AL_CONST char *s, int *len, int *ulen)); AL_FUNC(int, ustrcmp, (AL_CONST char *s1, AL_CONST char *s2)); AL_FUNC(char *, ustrzncpy, (char *dest, int size, AL_CONST char *src, int n)); AL_FUNC(char *, ustrzncat, (char *dest, int size, AL_CONST char *src, int n)); diff --git a/libsrc/allegro/src/unicode.c b/libsrc/allegro/src/unicode.c index 4e3a1f303b6..0a2676b9b4c 100644 --- a/libsrc/allegro/src/unicode.c +++ b/libsrc/allegro/src/unicode.c @@ -1900,6 +1900,25 @@ int ustrlen(AL_CONST char *s) +/* ustrlen2: + * Similar to ustrlen, but returns both length in characters + and length in bytes. + */ +void ustrlen2(AL_CONST char *s, int *len, int *ulen) +{ + int c = 0; + AL_CONST char *src = s; + ASSERT(s); + + while (ugetxc(&s)) + c++; + + *len = (int)(s - src) - 1; + *ulen = c; +} + + + /* ustrcmp: * Unicode-aware version of the ANSI strcmp() function. */ From e2beb30a5dc31eab7f0cf0f65c265e1a8e78a1db Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Wed, 25 Oct 2023 02:35:45 +0300 Subject: [PATCH 4/6] Engine: in String's API functions try reuse known unicode lengths --- Engine/ac/dynobj/scriptstring.cpp | 32 ++++++++++++++++--------------- Engine/ac/dynobj/scriptstring.h | 12 ++++++++---- Engine/ac/file.cpp | 2 +- Engine/ac/string.cpp | 18 +++++++++-------- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/Engine/ac/dynobj/scriptstring.cpp b/Engine/ac/dynobj/scriptstring.cpp index 2db5339085f..3d8ff5fd254 100644 --- a/Engine/ac/dynobj/scriptstring.cpp +++ b/Engine/ac/dynobj/scriptstring.cpp @@ -60,11 +60,8 @@ void ScriptString::Unserialize(int index, Stream *in, size_t /*data_sz*/) ccRegisterUnserializedObject(index, text_ptr, this); } -DynObjectRef ScriptString::CreateObject(uint8_t *buf, size_t len, size_t ulen) +DynObjectRef ScriptString::CreateObject(uint8_t *buf) { - Header &hdr = reinterpret_cast(*buf); - hdr.Length = len; - hdr.ULength = ulen; char *text_ptr = reinterpret_cast(buf + MemHeaderSz); int32_t handle = ccRegisterManagedObject(text_ptr, &myScriptStringImpl); if (handle == 0) @@ -75,27 +72,32 @@ DynObjectRef ScriptString::CreateObject(uint8_t *buf, size_t len, size_t ulen) return DynObjectRef(handle, text_ptr, &myScriptStringImpl); } -ScriptString::Buffer ScriptString::CreateBuffer(size_t data_sz) +ScriptString::Buffer ScriptString::CreateBuffer(size_t len, size_t ulen) { - std::unique_ptr buf(new uint8_t[data_sz + MemHeaderSz]); - return Buffer(std::move(buf), data_sz + MemHeaderSz); + assert(ulen <= len); + std::unique_ptr buf(new uint8_t[len + 1 + MemHeaderSz]); + auto *header = reinterpret_cast(buf.get()); + header->Length = len; + header->ULength = ulen; + return Buffer(std::move(buf), len + 1 + MemHeaderSz); } DynObjectRef ScriptString::Create(const char *text) { int len, ulen; ustrlen2(text, &len, &ulen); - uint8_t *buf = new uint8_t[len + 1 + MemHeaderSz]; - char *text_ptr = reinterpret_cast(buf + MemHeaderSz); - memcpy(buf, text, len + 1); - return CreateObject(buf, len, ulen); + auto buf = CreateBuffer(len, ulen); + memcpy(buf.Get(), text, len + 1); + return CreateObject(buf._buf.release()); } DynObjectRef ScriptString::Create(Buffer &&strbuf) { - int len, ulen; - ustrlen2(strbuf.Get(), &len, &ulen); uint8_t *buf = strbuf._buf.release(); - strbuf._sz = 0u; - return CreateObject(buf, len, ulen); + auto *header = reinterpret_cast(buf); + char *text_ptr = reinterpret_cast(buf + MemHeaderSz); + if ((header->Length > 0) && (header->ULength == 0u)) + header->ULength = ustrlen(text_ptr); + text_ptr[header->Length] = 0; // for safety + return CreateObject(buf); } diff --git a/Engine/ac/dynobj/scriptstring.h b/Engine/ac/dynobj/scriptstring.h index be26ec0efd1..e2790789b9b 100644 --- a/Engine/ac/dynobj/scriptstring.h +++ b/Engine/ac/dynobj/scriptstring.h @@ -55,11 +55,15 @@ struct ScriptString final : AGSCCDynamicObject return reinterpret_cast(*(static_cast(address) - MemHeaderSz)); } - // - static Buffer CreateBuffer(size_t data_sz); + // Allocates a ScriptString-compatible buffer large enough to accomodate + // given length in bytes (len). This buffer may be filled by the caller + // and then passed into Create(). If ulen is left eq 0, then it will be + // recalculated on script string's creation. + static Buffer CreateBuffer(size_t len, size_t ulen = 0u); // Create a new script string by copying the given text static DynObjectRef Create(const char *text); - // + // Create a new script string by taking ownership over the given buffer; + // passed buffer variable becomes invalid after this call. static DynObjectRef Create(Buffer &&strbuf); const char *GetType() override; @@ -73,7 +77,7 @@ struct ScriptString final : AGSCCDynamicObject // The size of the serialized header static const size_t FileHeaderSz = sizeof(uint32_t); - static DynObjectRef CreateObject(uint8_t *buf, size_t len, size_t ulen); + static DynObjectRef CreateObject(uint8_t *buf); // Savegame serialization // Calculate and return required space for serialization, in bytes diff --git a/Engine/ac/file.cpp b/Engine/ac/file.cpp index e6faf0e2618..fd5b68bb283 100644 --- a/Engine/ac/file.cpp +++ b/Engine/ac/file.cpp @@ -173,7 +173,7 @@ const char* File_ReadStringBack(sc_File *fil) { return CreateNewScriptString("");; } - auto buf = ScriptString::CreateBuffer(data_sz); // stored len + 1 + auto buf = ScriptString::CreateBuffer(data_sz - 1); // stored len + 1 in->Read(buf.Get(), data_sz); return CreateNewScriptString(std::move(buf)); } diff --git a/Engine/ac/string.cpp b/Engine/ac/string.cpp index c2c7db272cb..95d02e7ce6e 100644 --- a/Engine/ac/string.cpp +++ b/Engine/ac/string.cpp @@ -13,6 +13,7 @@ //============================================================================= #include #include +#include #include "ac/string.h" #include "ac/common.h" #include "ac/display.h" @@ -53,8 +54,9 @@ const char* String_Copy(const char *srcString) { const char* String_Append(const char *thisString, const char *extrabit) { const auto &header = ScriptString::GetHeader(thisString); - size_t str2_len = strlen(extrabit); - auto buf = ScriptString::CreateBuffer(header.Length + str2_len + 1); + int str2_len, str2_ulen; + ustrlen2(extrabit, &str2_len, &str2_ulen); + auto buf = ScriptString::CreateBuffer(header.Length + str2_len, header.ULength + str2_ulen); memcpy(buf.Get(), thisString, header.Length); memcpy(buf.Get() + header.Length, extrabit, str2_len + 1); return CreateNewScriptString(std::move(buf)); @@ -64,7 +66,7 @@ const char* String_AppendChar(const char *thisString, int extraOne) { char chr[5]{}; const auto &header = ScriptString::GetHeader(thisString); size_t new_chw = usetc(chr, extraOne); - auto buf = ScriptString::CreateBuffer(header.Length + new_chw + 1); + auto buf = ScriptString::CreateBuffer(header.Length + new_chw, header.ULength + 1); memcpy(buf.Get(), thisString, header.Length); memcpy(buf.Get() + header.Length, chr, new_chw + 1); return CreateNewScriptString(std::move(buf)); @@ -81,7 +83,7 @@ const char* String_ReplaceCharAt(const char *thisString, int index, int newChar) char new_chr[5]{}; size_t new_chw = usetc(new_chr, newChar); size_t new_len = header.Length + new_chw - old_chw; - auto buf = ScriptString::CreateBuffer(new_len + 1); + auto buf = ScriptString::CreateBuffer(new_len, header.ULength); // text length is the same memcpy(buf.Get(), thisString, off); memcpy(buf.Get() + off, new_chr, new_chw); memcpy(buf.Get() + off + new_chw, thisString + off + old_chw, header.Length - off - old_chw + 1); @@ -96,7 +98,7 @@ const char* String_Truncate(const char *thisString, int length) { return thisString; size_t new_len = uoffset(thisString, length); - auto buf = ScriptString::CreateBuffer(new_len + 1); + auto buf = ScriptString::CreateBuffer(new_len, length); // arg is a text length memcpy(buf.Get(), thisString, new_len); buf.Get()[new_len] = 0; return CreateNewScriptString(std::move(buf)); @@ -113,7 +115,7 @@ const char* String_Substring(const char *thisString, int index, int length) { size_t end = uoffset(thisString + start, sublen) + start; size_t copylen = end - start; - auto buf = ScriptString::CreateBuffer(copylen + 1); + auto buf = ScriptString::CreateBuffer(copylen, length); // arg is a text length memcpy(buf.Get(), thisString + start, copylen); buf.Get()[copylen] = 0; return CreateNewScriptString(std::move(buf)); @@ -207,7 +209,7 @@ const char* String_Replace(const char *thisString, const char *lookForText, cons const char* String_LowerCase(const char *thisString) { const auto &header = ScriptString::GetHeader(thisString); - auto buf = ScriptString::CreateBuffer(header.Length + 1); + auto buf = ScriptString::CreateBuffer(header.Length, header.ULength); memcpy(buf.Get(), thisString, header.Length + 1); ustrlwr(buf.Get()); return CreateNewScriptString(std::move(buf)); @@ -215,7 +217,7 @@ const char* String_LowerCase(const char *thisString) { const char* String_UpperCase(const char *thisString) { const auto &header = ScriptString::GetHeader(thisString); - auto buf = ScriptString::CreateBuffer(header.Length + 1); + auto buf = ScriptString::CreateBuffer(header.Length, header.ULength); memcpy(buf.Get(), thisString, header.Length + 1); ustrupr(buf.Get()); return CreateNewScriptString(std::move(buf)); From 1ae98d97fd6ca5631c25e9797e409a50610c90a7 Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Thu, 26 Oct 2023 17:10:28 +0300 Subject: [PATCH 5/6] Engine: speed up String.Chars[] iteration in unicode mode This speeds up utf-8 string iteration by remembering last requested char index and buffer offset. This seems to be the only solution in the absence of proper ScriptString iterators in script API. Costs 4 extra bytes per managed String object. I intentionally limit these 2 indexes to uint16_t (64k char/byte) to save bit of mem. On average Strings are not that long, in the worst case things may be sped up by splitting into substrings in the script. This change does not impose any dependencies, does not change save format, and may be safely reverted anytime. --- Engine/ac/dynobj/scriptstring.cpp | 2 ++ Engine/ac/dynobj/scriptstring.h | 13 ++++++++++++- Engine/ac/string.cpp | 13 +++++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/Engine/ac/dynobj/scriptstring.cpp b/Engine/ac/dynobj/scriptstring.cpp index 3d8ff5fd254..8099c491bde 100644 --- a/Engine/ac/dynobj/scriptstring.cpp +++ b/Engine/ac/dynobj/scriptstring.cpp @@ -79,6 +79,8 @@ ScriptString::Buffer ScriptString::CreateBuffer(size_t len, size_t ulen) auto *header = reinterpret_cast(buf.get()); header->Length = len; header->ULength = ulen; + header->LastCharIdx = 0; + header->LastCharOff = 0; return Buffer(std::move(buf), len + 1 + MemHeaderSz); } diff --git a/Engine/ac/dynobj/scriptstring.h b/Engine/ac/dynobj/scriptstring.h index e2790789b9b..aea951ce5e0 100644 --- a/Engine/ac/dynobj/scriptstring.h +++ b/Engine/ac/dynobj/scriptstring.h @@ -23,7 +23,13 @@ struct ScriptString final : AGSCCDynamicObject struct Header { uint32_t Length = 0u; // string length in bytes (not counting 0) - uint32_t ULength = 0u; // UTF-8 compatible length in characters + uint32_t ULength = 0u; // Unicode compatible length in characters + // Saved last requested character index and buffer offset; + // significantly speeds up Unicode string iteration, but adds 4 bytes + // per ScriptString object. Replace with a proper str iterator later! + // NOTE: intentionally limited to 64k chars/bytes to save bit of mem. + uint16_t LastCharIdx = 0u; + uint16_t LastCharOff = 0u; }; struct Buffer @@ -55,6 +61,11 @@ struct ScriptString final : AGSCCDynamicObject return reinterpret_cast(*(static_cast(address) - MemHeaderSz)); } + inline static Header &GetHeader(void *address) + { + return reinterpret_cast(*(static_cast(address) - MemHeaderSz)); + } + // Allocates a ScriptString-compatible buffer large enough to accomodate // given length in bytes (len). This buffer may be filled by the caller // and then passed into Create(). If ulen is left eq 0, then it will be diff --git a/Engine/ac/string.cpp b/Engine/ac/string.cpp index 95d02e7ce6e..0351f0c4e36 100644 --- a/Engine/ac/string.cpp +++ b/Engine/ac/string.cpp @@ -224,10 +224,19 @@ const char* String_UpperCase(const char *thisString) { } int String_GetChars(const char *thisString, int index) { - const auto &header = ScriptString::GetHeader(thisString); + auto &header = ScriptString::GetHeader((void*)thisString); if ((index < 0) || (static_cast(index) >= header.ULength)) return 0; - return ugetat(thisString, index); + int off = (header.LastCharIdx <= index) ? + (uoffset(thisString + header.LastCharOff, index - header.LastCharIdx) + header.LastCharOff) : + uoffset(thisString, index); + // NOTE: works up to 64k chars/bytes, then stops; this is intentional to save a bit of mem + if (off <= UINT16_MAX) + { + header.LastCharIdx = static_cast(index); + header.LastCharOff = static_cast(off); + } + return ugetc(thisString + off); } int StringToInt(const char*stino) { From 6cce9f8fffa58634497a6eb0e1d63fad872dedea Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Mon, 30 Oct 2023 22:41:29 +0300 Subject: [PATCH 6/6] Engine: even faster String.Chars[] variant for ASCII mode --- Engine/ac/string.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Engine/ac/string.cpp b/Engine/ac/string.cpp index 0351f0c4e36..ce0e0caa485 100644 --- a/Engine/ac/string.cpp +++ b/Engine/ac/string.cpp @@ -227,9 +227,20 @@ int String_GetChars(const char *thisString, int index) { auto &header = ScriptString::GetHeader((void*)thisString); if ((index < 0) || (static_cast(index) >= header.ULength)) return 0; - int off = (header.LastCharIdx <= index) ? - (uoffset(thisString + header.LastCharOff, index - header.LastCharIdx) + header.LastCharOff) : - uoffset(thisString, index); + int off; + if (get_uformat() == U_ASCII) + { + return thisString[index]; + } + else if (header.LastCharIdx <= index) + { + off = uoffset(thisString + header.LastCharOff, index - header.LastCharIdx) + header.LastCharOff; + } + // TODO: support faster reverse iteration too? that would require reverse-dir uoffset + else + { + off = uoffset(thisString, index); + } // NOTE: works up to 64k chars/bytes, then stops; this is intentional to save a bit of mem if (off <= UINT16_MAX) {