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..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; } }; @@ -105,10 +106,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..8099c491bde 100644 --- a/Engine/ac/dynobj/scriptstring.cpp +++ b/Engine/ac/dynobj/scriptstring.cpp @@ -14,65 +14,92 @@ #include "ac/dynobj/scriptstring.h" #include #include +#include #include "ac/string.h" #include "ac/dynobj/dynobj_manager.h" #include "util/stream.h" using namespace AGS::Common; +ScriptString myScriptStringImpl; -DynObjectRef ScriptString::CreateString(const char *fromText) { - return CreateNewScriptStringObj(fromText); +const char *ScriptString::GetType() +{ + return "String"; } -int ScriptString::Dispose(void* /*address*/, bool /*force*/) { - // always dispose - if (_text) { - free(_text); - _text = nullptr; - } - delete this; +int ScriptString::Dispose(void *address, bool /*force*/) +{ + delete[] (static_cast(address) - MemHeaderSz); return 1; } -const char *ScriptString::GetType() { - return "String"; +size_t ScriptString::CalcSerializeSize(const void *address) +{ + const Header &hdr = GetHeader(address); + return hdr.Length + 1 + FileHeaderSz; } -size_t ScriptString::CalcSerializeSize(const void* /*address*/) +void ScriptString::Serialize(const void* address, Stream *out) { - return _len + 1 + sizeof(int32_t); + const Header &hdr = GetHeader(address); + out->WriteInt32(hdr.Length); + out->Write(address, hdr.Length + 1); // it was writing trailing 0 for some reason } -void ScriptString::Serialize(const void* /*address*/, Stream *out) { - const auto *cstr = _text ? _text : ""; - out->WriteInt32(_len); - out->Write(cstr, _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]; + 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); } -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); +DynObjectRef ScriptString::CreateObject(uint8_t *buf) +{ + char *text_ptr = reinterpret_cast(buf + MemHeaderSz); + int32_t handle = ccRegisterManagedObject(text_ptr, &myScriptStringImpl); + if (handle == 0) + { + delete[] buf; + return DynObjectRef(); + } + return DynObjectRef(handle, text_ptr, &myScriptStringImpl); } -ScriptString::ScriptString(const char *text) { - _len = strlen(text); - _text = (char*)malloc(_len + 1); - memcpy(_text, text, _len + 1); +ScriptString::Buffer ScriptString::CreateBuffer(size_t len, size_t ulen) +{ + 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; + header->LastCharIdx = 0; + header->LastCharOff = 0; + return Buffer(std::move(buf), len + 1 + MemHeaderSz); } -ScriptString::ScriptString(char *text, bool take_ownership) { - _len = strlen(text); - if (take_ownership) - { - _text = text; - } - else - { - _text = (char*)malloc(_len + 1); - memcpy(_text, text, _len + 1); - } +DynObjectRef ScriptString::Create(const char *text) +{ + int len, ulen; + ustrlen2(text, &len, &ulen); + auto buf = CreateBuffer(len, ulen); + memcpy(buf.Get(), text, len + 1); + return CreateObject(buf._buf.release()); +} + +DynObjectRef ScriptString::Create(Buffer &&strbuf) +{ + uint8_t *buf = strbuf._buf.release(); + 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 12bcdc1e1b6..aea951ce5e0 100644 --- a/Engine/ac/dynobj/scriptstring.h +++ b/Engine/ac/dynobj/scriptstring.h @@ -14,31 +14,89 @@ #ifndef __AC_SCRIPTSTRING_H #define __AC_SCRIPTSTRING_H +#include #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; // string length in bytes (not counting 0) + 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 + { + friend ScriptString; + public: + Buffer() = default; + ~Buffer() = default; + Buffer(Buffer &&buf) = default; + // 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) + : _buf(std::move(buf)), _sz(buf_sz) {} + + std::unique_ptr _buf; + size_t _sz; + }; + + + ScriptString() = default; + ~ScriptString() = default; + + inline static const Header &GetHeader(const void *address) + { + 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 + // 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; + 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: + 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); - ScriptString() = default; - ScriptString(const char *text); - ScriptString(char *text, bool take_ownership); - char *GetTextPtr() const { return _text; } + static DynObjectRef CreateObject(uint8_t *buf); -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..fd5b68bb283 100644 --- a/Engine/ac/file.cpp +++ b/Engine/ac/file.cpp @@ -166,17 +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 *retVal = (char*)malloc(lle); - in->Read(retVal, lle); - - return CreateNewScriptString(retVal, false); + auto buf = ScriptString::CreateBuffer(data_sz - 1); // stored len + 1 + in->Read(buf.Get(), data_sz); + return CreateNewScriptString(std::move(buf)); } 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..ce0e0caa485 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" @@ -32,7 +33,12 @@ using namespace AGS::Common; extern GameSetupStruct game; extern GameState play; extern int longestline; -extern ScriptString myScriptStringImpl; + +const char *CreateNewScriptString(const char *text) +{ + return static_cast(ScriptString::Create(text).Obj); +} + int String_IsNullOrEmpty(const char *thisString) { @@ -47,68 +53,72 @@ 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); - strcpy(buffer, thisString); - strcat(buffer, extrabit); - return CreateNewScriptString(buffer, false); + const auto &header = ScriptString::GetHeader(thisString); + 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)); } 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); - sprintf(buffer, "%s%s", thisString, chr); - return CreateNewScriptString(buffer, false); + const auto &header = ScriptString::GetHeader(thisString); + size_t new_chw = usetc(chr, extraOne); + 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)); } 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 uchar = ugetc(thisString + off); - size_t remain_sz = strlen(thisString + off); - size_t old_sz = ucwidth(uchar); + int old_char = ugetc(thisString + off); + size_t old_chw = ucwidth(old_char); 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); - 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); + size_t new_len = header.Length + new_chw - old_chw; + 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); + 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 sz = uoffset(thisString, length); - char *buffer = (char*)malloc(sz + 1); - memcpy(buffer, thisString, sz); - buffer[sz] = 0; - return CreateNewScriptString(buffer, false); + size_t new_len = uoffset(thisString, length); + 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)); } 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 copysz = end - start; + size_t copylen = end - start; - char *buffer = (char*)malloc(copysz + 1); - memcpy(buffer, thisString + start, copysz); - buffer[copysz] = 0; - return CreateNewScriptString(buffer, false); + 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)); } int String_CompareTo(const char *thisString, const char *otherString, bool caseSensitive) { @@ -133,17 +143,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; } } @@ -193,25 +204,50 @@ 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); - ustrlwr(buffer); - return CreateNewScriptString(buffer, false); + const auto &header = ScriptString::GetHeader(thisString); + auto buf = ScriptString::CreateBuffer(header.Length, header.ULength); + memcpy(buf.Get(), thisString, header.Length + 1); + ustrlwr(buf.Get()); + return CreateNewScriptString(std::move(buf)); } const char* String_UpperCase(const char *thisString) { - char *buffer = ags_strdup(thisString); - ustrupr(buffer); - return CreateNewScriptString(buffer, false); + const auto &header = ScriptString::GetHeader(thisString); + auto buf = ScriptString::CreateBuffer(header.Length, header.ULength); + 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) { + auto &header = ScriptString::GetHeader((void*)thisString); + if ((index < 0) || (static_cast(index) >= header.ULength)) return 0; - return ugetat(texx, 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) + { + header.LastCharIdx = static_cast(index); + header.LastCharOff = static_cast(off); + } + return ugetc(thisString + off); } int StringToInt(const char*stino) { @@ -242,38 +278,11 @@ int StrContains (const char *s1, const char *s2) { return at; } -//============================================================================= - -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; +int String_GetLength(const char *thisString) { + return ScriptString::GetHeader(thisString).ULength; } -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) @@ -328,14 +337,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 @@ -467,10 +468,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)); } //============================================================================= @@ -510,7 +511,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/Engine/ac/string.h b/Engine/ac/string.h index 3d5d114b308..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,6 +30,12 @@ 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()); } +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); const char* String_Append(const char *thisString, const char *extrabit); @@ -49,10 +55,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: 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. */