From 3523a4908698b145ffdfc2407dd4a3f40a998e41 Mon Sep 17 00:00:00 2001 From: vladislavbelov Date: Sun, 13 Feb 2022 22:31:36 +0000 Subject: [PATCH] Removes unused and redundant h_mgr after rP25936 and rP26368. git-svn-id: https://svn.wildfiregames.com/public/ps/trunk@26369 3db68df2-c116-0410-a063-a993310a9797 --- source/graphics/MapReader.h | 3 +- source/graphics/MiniPatch.h | 8 +- source/graphics/TerrainTextureManager.h | 1 - source/graphics/tests/test_TextureManager.h | 7 +- source/lib/res/h_mgr.cpp | 789 ---------------------------- source/lib/res/h_mgr.h | 432 --------------- source/lib/res/handle.h | 43 -- source/lib/tex/tex.h | 1 - source/ps/Filesystem.cpp | 11 +- source/ps/GameSetup/GameSetup.cpp | 8 - source/ps/Replay.cpp | 4 - 11 files changed, 8 insertions(+), 1299 deletions(-) delete mode 100644 source/lib/res/h_mgr.cpp delete mode 100644 source/lib/res/h_mgr.h delete mode 100644 source/lib/res/handle.h diff --git a/source/graphics/MapReader.h b/source/graphics/MapReader.h index a87cb90503..be423b41e3 100644 --- a/source/graphics/MapReader.h +++ b/source/graphics/MapReader.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -21,7 +21,6 @@ #include "MapIO.h" #include "graphics/LightEnv.h" -#include "lib/res/handle.h" #include "ps/CStr.h" #include "ps/FileIo.h" #include "scriptinterface/ScriptTypes.h" diff --git a/source/graphics/MiniPatch.h b/source/graphics/MiniPatch.h index 85fa50b9b8..d91324ef80 100644 --- a/source/graphics/MiniPatch.h +++ b/source/graphics/MiniPatch.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -22,11 +22,8 @@ #ifndef INCLUDED_MINIPATCH #define INCLUDED_MINIPATCH -#include "lib/res/handle.h" - class CTerrainTextureEntry; -/////////////////////////////////////////////////////////////////////////////// // CMiniPatch: definition of a single terrain tile class CMiniPatch { @@ -43,5 +40,4 @@ public: int GetPriority() { return Priority; } }; - -#endif +#endif // INCLUDED_MINIPATCH diff --git a/source/graphics/TerrainTextureManager.h b/source/graphics/TerrainTextureManager.h index c9c7699e28..93231ef9e2 100644 --- a/source/graphics/TerrainTextureManager.h +++ b/source/graphics/TerrainTextureManager.h @@ -19,7 +19,6 @@ #define INCLUDED_TERRAINTEXTUREMANAGER #include "lib/file/vfs/vfs_path.h" -#include "lib/res/handle.h" #include "ps/CStr.h" #include "ps/Singleton.h" #include "renderer/backend/gl/DeviceCommandContext.h" diff --git a/source/graphics/tests/test_TextureManager.h b/source/graphics/tests/test_TextureManager.h index e949aa26c9..0e810ff40e 100644 --- a/source/graphics/tests/test_TextureManager.h +++ b/source/graphics/tests/test_TextureManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -20,7 +20,6 @@ #include "graphics/TextureManager.h" #include "lib/external_libraries/libsdl.h" #include "lib/file/vfs/vfs.h" -#include "lib/res/h_mgr.h" #include "lib/tex/tex.h" #include "lib/ogl.h" #include "ps/XML/Xeromyces.h" @@ -39,8 +38,6 @@ public: TS_ASSERT_OK(m_VFS->Mount(L"", DataDir() / "mods" / "_test.tex" / "", VFS_MOUNT_MUST_EXIST)); TS_ASSERT_OK(m_VFS->Mount(L"cache/", DataDir() / "_testcache" / "", 0, VFS_MAX_PRIORITY)); - h_mgr_init(); - CXeromyces::Startup(); } @@ -48,8 +45,6 @@ public: { CXeromyces::Terminate(); - h_mgr_shutdown(); - m_VFS.reset(); DeleteDirectory(DataDir()/"_testcache"); } diff --git a/source/lib/res/h_mgr.cpp b/source/lib/res/h_mgr.cpp deleted file mode 100644 index e03317d2a9..0000000000 --- a/source/lib/res/h_mgr.cpp +++ /dev/null @@ -1,789 +0,0 @@ -/* Copyright (C) 2019 Wildfire Games. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * handle manager for resources. - */ - -#include "precompiled.h" -#include "h_mgr.h" - -#include - -#include // CHAR_BIT -#include -#include -#include // std::bad_alloc - -#include "lib/fnv_hash.h" -#include "lib/allocators/overrun_protector.h" -#include "lib/allocators/pool.h" -#include "lib/module_init.h" - -#include - -namespace ERR { -static const Status H_IDX_INVALID = -120000; // totally invalid -static const Status H_IDX_UNUSED = -120001; // beyond current cap -static const Status H_TAG_MISMATCH = -120003; -static const Status H_TYPE_MISMATCH = -120004; -static const Status H_ALREADY_FREED = -120005; -} -static const StatusDefinition hStatusDefinitions[] = { - { ERR::H_IDX_INVALID, L"Handle index completely out of bounds" }, - { ERR::H_IDX_UNUSED, L"Handle index exceeds high-water mark" }, - { ERR::H_TAG_MISMATCH, L"Handle tag mismatch (stale reference?)" }, - { ERR::H_TYPE_MISMATCH, L"Handle type mismatch" }, - { ERR::H_ALREADY_FREED, L"Handle already freed" } -}; -STATUS_ADD_DEFINITIONS(hStatusDefinitions); - - - -// rationale -// -// why fixed size control blocks, instead of just allocating dynamically? -// it is expected that resources be created and freed often. this way is -// much nicer to the memory manager. defining control blocks larger than -// the allotted space is caught by h_alloc (made possible by the vtbl builder -// storing control block size). it is also efficient to have all CBs in an -// more or less contiguous array (see below). -// -// why a manager, instead of a simple pool allocator? -// we need a central list of resources for freeing at exit, checking if a -// resource has already been loaded (for caching), and when reloading. -// may as well keep them in an array, rather than add a list and index. - - - -// -// handle -// - -// 0 = invalid handle value -// < 0 is an error code (we assume < 0 <==> MSB is set - -// true for 1s and 2s complement and sign-magnitude systems) - -// fields: -// (shift value = # bits between LSB and field LSB. -// may be larger than the field type - only shift Handle vars!) - -// - index (0-based) of control block in our array. -// (field width determines maximum currently open handles) -#define IDX_BITS 16 -static const u64 IDX_MASK = (1l << IDX_BITS) - 1; - -// - tag (1-based) ensures the handle references a certain resource instance. -// (field width determines maximum unambiguous resource allocs) -using Tag = i64; -#define TAG_BITS 48 - -// make sure both fields fit within a Handle variable -cassert(IDX_BITS + TAG_BITS <= sizeof(Handle)*CHAR_BIT); - - -// return the handle's index field (always non-negative). -// no error checking! -static inline size_t h_idx(const Handle h) -{ - return (size_t)(h & IDX_MASK) - 1; -} - -// build a handle from index and tag. -// can't fail. -static inline Handle handle(size_t idx, u64 tag) -{ - const size_t idxPlusOne = idx+1; - ENSURE(idxPlusOne <= IDX_MASK); - ENSURE((tag & IDX_MASK) == 0); - Handle h = tag | idxPlusOne; - ENSURE(h > 0); - return h; -} - - -// -// internal per-resource-instance data -// - -// chosen so that all current resource structs are covered. -static const size_t HDATA_USER_SIZE = 104; - - -struct HDATA -{ - // we only need the tag, because it is trivial to compute - // &HDATA from idx and vice versa. storing the entire handle - // avoids needing to extract the tag field. - Handle h; // NB: will be overwritten by pool_free - - uintptr_t key; - - intptr_t refs; - - // smaller bit fields combined into 1 - // .. if set, do not actually release the resource (i.e. call dtor) - // when the handle is h_free-d, regardless of the refcount. - // set by h_alloc; reset on exit and by housekeeping. - u32 keep_open : 1; - // .. HACK: prevent adding to h_find lookup index if flags & RES_UNIQUE - // (because those handles might have several instances open, - // which the index can't currently handle) - u32 unique : 1; - u32 disallow_reload : 1; - - H_Type type; - - // for statistics - size_t num_derefs; - - // storing PIVFS here is not a good idea since this often isn't - // `freed' due to caching (and there is no dtor), so - // the VFS reference count would never reach zero. - VfsPath pathname; - - u8 user[HDATA_USER_SIZE]; -}; - - -// max data array entries. compared to last_in_use => signed. -static const ssize_t hdata_cap = (1ul << IDX_BITS)/4; - -// pool of fixed-size elements allows O(1) alloc and free; -// there is a simple mapping between HDATA address and index. -static Pool hpool; - - -// error checking strategy: -// all handles passed in go through h_data(Handle, Type) - - -// get a (possibly new) array entry. -// -// fails if idx is out of bounds. -static Status h_data_from_idx(ssize_t idx, HDATA*& hd) -{ - // don't check if idx is beyond the current high-water mark, because - // we might be allocating a new entry. subsequent tag checks protect - // against using unallocated entries. - if(size_t(idx) >= size_t(hdata_cap)) // also detects negative idx - WARN_RETURN(ERR::H_IDX_INVALID); - - hd = (HDATA*)(hpool.da.base + idx*hpool.el_size); - hd->num_derefs++; - return INFO::OK; -} - -static ssize_t h_idx_from_data(HDATA* hd) -{ - if(!pool_contains(&hpool, hd)) - WARN_RETURN(ERR::INVALID_POINTER); - return (uintptr_t(hd) - uintptr_t(hpool.da.base))/hpool.el_size; -} - - -// get HDATA for the given handle. -// only uses (and checks) the index field. -// used by h_force_close (which must work regardless of tag). -static inline Status h_data_no_tag(const Handle h, HDATA*& hd) -{ - ssize_t idx = (ssize_t)h_idx(h); - RETURN_STATUS_IF_ERR(h_data_from_idx(idx, hd)); - // need to verify it's in range - h_data_from_idx can only verify that - // it's < maximum allowable index. - if(uintptr_t(hd) > uintptr_t(hpool.da.base)+hpool.da.pos) - WARN_RETURN(ERR::H_IDX_UNUSED); - return INFO::OK; -} - - -static bool ignoreDoubleFree = false; - -// get HDATA for the given handle. -// also verifies the tag field. -// used by functions callable for any handle type, e.g. h_filename. -static inline Status h_data_tag(Handle h, HDATA*& hd) -{ - RETURN_STATUS_IF_ERR(h_data_no_tag(h, hd)); - - if(hd->key == 0) // HDATA was wiped out and hd->h overwritten by pool_free - { - if(ignoreDoubleFree) - return ERR::H_ALREADY_FREED; // NOWARN (see ignoreDoubleFree) - else - WARN_RETURN(ERR::H_ALREADY_FREED); - } - - if(h != hd->h) - WARN_RETURN(ERR::H_TAG_MISMATCH); - - return INFO::OK; -} - - -// get HDATA for the given handle. -// also verifies the type. -// used by most functions accessing handle data. -static Status h_data_tag_type(const Handle h, const H_Type type, HDATA*& hd) -{ - RETURN_STATUS_IF_ERR(h_data_tag(h, hd)); - - // h_alloc makes sure type isn't 0, so no need to check that here. - if(hd->type != type) - { - debug_printf("h_mgr: expected type %s, got %s\n", utf8_from_wstring(hd->type->name).c_str(), utf8_from_wstring(type->name).c_str()); - WARN_RETURN(ERR::H_TYPE_MISMATCH); - } - - return INFO::OK; -} - - -//----------------------------------------------------------------------------- -// lookup data structure -//----------------------------------------------------------------------------- - -// speed up h_find (called every h_alloc) -// multimap, because we want to add handles of differing type but same key -// (e.g. a VFile and Tex object for the same underlying filename hash key) -// -// store index because it's smaller and Handle can easily be reconstructed -// -// -// note: there may be several RES_UNIQUE handles of the same type and key -// (e.g. sound files - several instances of a sound definition file). -// that wasn't foreseen here, so we'll just refrain from adding to the index. -// that means they won't be found via h_find - no biggie. - -using Key2Idx = std::unordered_multimap; -using It = Key2Idx::iterator; -static OverrunProtector key2idx_wrapper; - -enum KeyRemoveFlag { KEY_NOREMOVE, KEY_REMOVE }; - -static Handle key_find(uintptr_t key, H_Type type, KeyRemoveFlag remove_option = KEY_NOREMOVE) -{ - Key2Idx* key2idx = key2idx_wrapper.get(); - if(!key2idx) - WARN_RETURN(ERR::NO_MEM); - - // initial return value: "not found at all, or it's of the - // wrong type". the latter happens when called by h_alloc to - // check if e.g. a Tex object already exists; at that time, - // only the corresponding VFile exists. - Handle ret = -1; - - std::pair range = key2idx->equal_range(key); - for(It it = range.first; it != range.second; ++it) - { - ssize_t idx = it->second; - HDATA* hd; - if(h_data_from_idx(idx, hd) != INFO::OK) - continue; - if(hd->type != type || hd->key != key) - continue; - - // found a match - if(remove_option == KEY_REMOVE) - key2idx->erase(it); - ret = hd->h; - break; - } - - key2idx_wrapper.lock(); - return ret; -} - - -static void key_add(uintptr_t key, Handle h) -{ - Key2Idx* key2idx = key2idx_wrapper.get(); - if(!key2idx) - return; - - const ssize_t idx = h_idx(h); - // note: MSDN documentation of stdext::hash_multimap is incorrect; - // there is no overload of insert() that returns pair. - (void)key2idx->insert(std::make_pair(key, idx)); - - key2idx_wrapper.lock(); -} - - -static void key_remove(uintptr_t key, H_Type type) -{ - Handle ret = key_find(key, type, KEY_REMOVE); - ENSURE(ret > 0); -} - - -//---------------------------------------------------------------------------- -// h_alloc -//---------------------------------------------------------------------------- - -static void warn_if_invalid(HDATA* hd) -{ -#ifndef NDEBUG - H_VTbl* vtbl = hd->type; - - // validate HDATA - // currently nothing to do; is checked by h_alloc and - // the others have no invariants we could check. - - // have the resource validate its user_data - Status err = vtbl->validate(hd->user); - ENSURE(err == INFO::OK); - - // make sure empty space in control block isn't touched - // .. but only if we're not storing a filename there - const u8* start = hd->user + vtbl->user_size; - const u8* end = hd->user + HDATA_USER_SIZE; - for(const u8* p = start; p < end; p++) - ENSURE(*p == 0); // else: handle user data was overrun! -#else - UNUSED2(hd); -#endif -} - - -static Status type_validate(H_Type type) -{ - if(!type) - WARN_RETURN(ERR::INVALID_PARAM); - if(type->user_size > HDATA_USER_SIZE) - WARN_RETURN(ERR::LIMIT); - if(type->name == 0) - WARN_RETURN(ERR::INVALID_PARAM); - - return INFO::OK; -} - - -static Tag gen_tag() -{ - static Tag tag; - tag += (1ull << IDX_BITS); - // it's not easy to detect overflow, because compilers - // are allowed to assume it'll never happen. however, - // pow(2, 64-IDX_BITS) is "enough" anyway. - return tag; -} - - -static Handle reuse_existing_handle(uintptr_t key, H_Type type, size_t flags) -{ - if(flags & RES_NO_CACHE) - return 0; - - // object of specified key and type doesn't exist yet - Handle h = h_find(type, key); - if(h <= 0) - return 0; - - HDATA* hd; - RETURN_STATUS_IF_ERR(h_data_tag_type(h, type, hd)); // h_find means this won't fail - - hd->refs += 1; - - // we are reactivating a closed but cached handle. - // need to generate a new tag so that copies of the - // previous handle can no longer access the resource. - // (we don't need to reset the tag in h_free, because - // use before this fails due to refs > 0 check in h_user_data). - if(hd->refs == 1) - { - const Tag tag = gen_tag(); - h = handle(h_idx(h), tag); // can't fail - hd->h = h; - } - - return h; -} - - -static Status call_init_and_reload(Handle h, H_Type type, HDATA* hd, const PIVFS& vfs, const VfsPath& pathname, va_list* init_args) -{ - Status err = INFO::OK; - H_VTbl* vtbl = type; // exact same thing but for clarity - - // init - if(vtbl->init) - vtbl->init(hd->user, *init_args); - - // reload - if(vtbl->reload) - { - // catch exception to simplify reload funcs - let them use new() - try - { - err = vtbl->reload(hd->user, vfs, pathname, h); - if(err == INFO::OK) - warn_if_invalid(hd); - } - catch(std::bad_alloc&) - { - err = ERR::NO_MEM; - } - } - - return err; -} - - -static Handle alloc_new_handle(H_Type type, const PIVFS& vfs, const VfsPath& pathname, uintptr_t key, size_t flags, va_list* init_args) -{ - HDATA* hd = (HDATA*)pool_alloc(&hpool, 0); - if(!hd) - WARN_RETURN(ERR::NO_MEM); - new(&hd->pathname) VfsPath; - - ssize_t idx = h_idx_from_data(hd); - RETURN_STATUS_IF_ERR(idx); - - // (don't want to do this before the add-reference exit, - // so as not to waste tags for often allocated handles.) - const Tag tag = gen_tag(); - Handle h = handle(idx, tag); // can't fail. - - hd->h = h; - hd->key = key; - hd->type = type; - hd->refs = 1; - if(!(flags & RES_NO_CACHE)) - hd->keep_open = 1; - if(flags & RES_DISALLOW_RELOAD) - hd->disallow_reload = 1; - hd->unique = (flags & RES_UNIQUE) != 0; - hd->pathname = pathname; - - if(key && !hd->unique) - key_add(key, h); - - Status err = call_init_and_reload(h, type, hd, vfs, pathname, init_args); - if(err < 0) - goto fail; - - return h; - -fail: - // reload failed; free the handle - hd->keep_open = 0; // disallow caching (since contents are invalid) - (void)h_free(h, type); // (h_free already does WARN_IF_ERR) - - // note: since some uses will always fail (e.g. loading sounds if - // g_Quickstart), do not complain here. - return (Handle)err; -} - - -static std::recursive_mutex h_mutex; - -// any further params are passed to type's init routine -Handle h_alloc(H_Type type, const PIVFS& vfs, const VfsPath& pathname, size_t flags, ...) -{ - std::lock_guard lock(h_mutex); - - RETURN_STATUS_IF_ERR(type_validate(type)); - - const uintptr_t key = fnv_hash(pathname.string().c_str(), pathname.string().length()*sizeof(pathname.string()[0])); - - // see if we can reuse an existing handle - Handle h = reuse_existing_handle(key, type, flags); - RETURN_STATUS_IF_ERR(h); - // .. successfully reused the handle; refcount increased - if(h > 0) - return h; - // .. need to allocate a new one: - va_list args; - va_start(args, flags); - h = alloc_new_handle(type, vfs, pathname, key, flags, &args); - va_end(args); - return h; // alloc_new_handle already does WARN_RETURN_STATUS_IF_ERR -} - - -//----------------------------------------------------------------------------- - -static void h_free_hd(HDATA* hd) -{ - if(hd->refs > 0) - hd->refs--; - - // still references open or caching requests it stays - do not release. - if(hd->refs > 0 || hd->keep_open) - return; - - // actually release the resource (call dtor, free control block). - - // h_alloc makes sure type != 0; if we get here, it still is - H_VTbl* vtbl = hd->type; - - // call its destructor - // note: H_TYPE_DEFINE currently always defines a dtor, but play it safe - if(vtbl->dtor) - vtbl->dtor(hd->user); - - if(hd->key && !hd->unique) - key_remove(hd->key, hd->type); - -#ifndef NDEBUG - // to_string is slow for some handles, so avoid calling it if unnecessary - if(debug_filter_allows("H_MGR|")) - { - wchar_t buf[H_STRING_LEN]; - if(vtbl->to_string(hd->user, buf) < 0) - wcscpy_s(buf, ARRAY_SIZE(buf), L"(error)"); - debug_printf("H_MGR| free %s %s accesses=%lu %s\n", utf8_from_wstring(hd->type->name).c_str(), hd->pathname.string8().c_str(), (unsigned long)hd->num_derefs, utf8_from_wstring(buf).c_str()); - } -#endif - - hd->pathname.~VfsPath(); // FIXME: ugly hack, but necessary to reclaim memory - memset(hd, 0, sizeof(*hd)); - pool_free(&hpool, hd); -} - - -Status h_free(Handle& h, H_Type type) -{ - std::lock_guard lock(h_mutex); - - // 0-initialized or an error code; don't complain because this - // happens often and is harmless. - if(h <= 0) - return INFO::OK; - - // wipe out the handle to prevent reuse but keep a copy for below. - const Handle h_copy = h; - h = 0; - - HDATA* hd; - RETURN_STATUS_IF_ERR(h_data_tag_type(h_copy, type, hd)); - - h_free_hd(hd); - return INFO::OK; -} - - -//---------------------------------------------------------------------------- -// remaining API - -void* h_user_data(const Handle h, const H_Type type) -{ - HDATA* hd; - if(h_data_tag_type(h, type, hd) != INFO::OK) - return 0; - - if(!hd->refs) - { - // note: resetting the tag is not enough (user might pass in its value) - DEBUG_WARN_ERR(ERR::LOGIC); // no references to resource (it's cached, but someone is accessing it directly) - return 0; - } - - warn_if_invalid(hd); - return hd->user; -} - - -VfsPath h_filename(const Handle h) -{ - // don't require type check: should be usable for any handle, - // even if the caller doesn't know its type. - HDATA* hd; - if(h_data_tag(h, hd) != INFO::OK) - return VfsPath(); - return hd->pathname; -} - - -// TODO: what if iterating through all handles is too slow? -Status h_reload(const PIVFS& vfs, const VfsPath& pathname) -{ - std::lock_guard lock(h_mutex); - - const u32 key = fnv_hash(pathname.string().c_str(), pathname.string().length()*sizeof(pathname.string()[0])); - - // destroy (note: not free!) all handles backed by this file. - // do this before reloading any of them, because we don't specify reload - // order (the parent resource may be reloaded first, and load the child, - // whose original data would leak). - for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size)) - { - if(hd->key == 0 || hd->key != key || hd->disallow_reload) - continue; - hd->type->dtor(hd->user); - } - - Status ret = INFO::OK; - - // now reload all affected handles - size_t i = 0; - for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size), i++) - { - if(hd->key == 0 || hd->key != key || hd->disallow_reload) - continue; - - Status err = hd->type->reload(hd->user, vfs, hd->pathname, hd->h); - // don't stop if an error is encountered - try to reload them all. - if(err < 0) - { - h_free(hd->h, hd->type); - if(ret == 0) // don't overwrite first error - ret = err; - } - else - warn_if_invalid(hd); - } - - return ret; -} - - -Handle h_find(H_Type type, uintptr_t key) -{ - std::lock_guard lock(h_mutex); - return key_find(key, type); -} - - - -// force the resource to be freed immediately, even if cached. -// tag is not checked - this allows the first Handle returned -// (whose tag will change after being 'freed', but remaining in memory) -// to later close the object. -// this is used when reinitializing the sound engine - -// at that point, all (cached) OpenAL resources must be freed. -Status h_force_free(Handle h, H_Type type) -{ - std::lock_guard lock(h_mutex); - - // require valid index; ignore tag; type checked below. - HDATA* hd; - RETURN_STATUS_IF_ERR(h_data_no_tag(h, hd)); - if(hd->type != type) - WARN_RETURN(ERR::H_TYPE_MISMATCH); - hd->keep_open = 0; - hd->refs = 0; - h_free_hd(hd); - return INFO::OK; -} - - -// increment Handle 's reference count. -// only meant to be used for objects that free a Handle in their dtor, -// so that they are copy-equivalent and can be stored in a STL container. -// do not use this to implement refcounting on top of the Handle scheme, -// e.g. loading a Handle once and then passing it around. instead, have each -// user load the resource; refcounting is done under the hood. -void h_add_ref(Handle h) -{ - HDATA* hd; - if(h_data_tag(h, hd) != INFO::OK) - return; - - ENSURE(hd->refs); // if there are no refs, how did the caller manage to keep a Handle?! - hd->refs++; -} - - -// retrieve the internal reference count or a negative error code. -// background: since h_alloc has no way of indicating whether it -// allocated a new handle or reused an existing one, counting references -// within resource control blocks is impossible. since that is sometimes -// necessary (always wrapping objects in Handles is excessive), we -// provide access to the internal reference count. -intptr_t h_get_refcnt(Handle h) -{ - HDATA* hd; - RETURN_STATUS_IF_ERR(h_data_tag(h, hd)); - - ENSURE(hd->refs); // if there are no refs, how did the caller manage to keep a Handle?! - return hd->refs; -} - - -static ModuleInitState initState; - -static Status Init() -{ - RETURN_STATUS_IF_ERR(pool_create(&hpool, hdata_cap*sizeof(HDATA), sizeof(HDATA))); - return INFO::OK; -} - -static void Shutdown() -{ - debug_printf("H_MGR| shutdown. any handle frees after this are leaks!\n"); - // objects that store handles to other objects are destroyed before their - // children, so the subsequent forced destruction of the child here will - // raise a double-free warning unless we ignore it. (#860, #915, #920) - ignoreDoubleFree = true; - - std::lock_guard lock(h_mutex); - - // forcibly close all open handles - for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size)) - { - // it's already been freed; don't free again so that this - // doesn't look like an error. - if(hd->key == 0) - continue; - - // disable caching; we need to release the resource now. - hd->keep_open = 0; - hd->refs = 0; - - h_free_hd(hd); - } - - pool_destroy(&hpool); -} - -void h_mgr_free_type(const H_Type type) -{ - ignoreDoubleFree = true; - - std::lock_guard lock(h_mutex); - - // forcibly close all open handles of the specified type - for(HDATA* hd = (HDATA*)hpool.da.base; hd < (HDATA*)(hpool.da.base + hpool.da.pos); hd = (HDATA*)(uintptr_t(hd)+hpool.el_size)) - { - // free if not previously freed and only free the proper type - if (hd->key == 0 || hd->type != type) - continue; - - // disable caching; we need to release the resource now. - hd->keep_open = 0; - hd->refs = 0; - - h_free_hd(hd); - } -} - -void h_mgr_init() -{ - ModuleInit(&initState, Init); -} - -void h_mgr_shutdown() -{ - ModuleShutdown(&initState, Shutdown); -} diff --git a/source/lib/res/h_mgr.h b/source/lib/res/h_mgr.h deleted file mode 100644 index 0a2e9b0496..0000000000 --- a/source/lib/res/h_mgr.h +++ /dev/null @@ -1,432 +0,0 @@ -/* Copyright (C) 2010 Wildfire Games. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * handle manager for resources. - */ - -/* - -[KEEP IN SYNC WITH WIKI] - -introduction ------------- - -a resource is an instance of a specific type of game data (e.g. texture), -described by a control block (example fields: format, pointer to tex data). - -this module allocates storage for the control blocks, which are accessed -via handle. it also provides support for transparently reloading resources -from disk (allows in-game editing of data), and caches resource data. -finally, it frees all resources at exit, preventing leaks. - - -handles -------- - -handles are an indirection layer between client code and resources -(represented by their control blocks, which contains/points to its data). -they allow an important check not possible with a direct pointer: -guaranteeing the handle references a given resource /instance/. - -problem: code C1 allocates a resource, and receives a pointer p to its -control block. C1 passes p on to C2, and later frees it. -now other code allocates a resource, and happens to reuse the free slot -pointed to by p (also possible if simply allocating from the heap). -when C2 accesses p, the pointer is valid, but we cannot tell that -it is referring to a resource that had already been freed. big trouble. - -solution: each allocation receives a unique tag (a global counter that -is large enough to never overflow). Handles include this tag, as well -as a reference (array index) to the control block, which isn't directly -accessible. when dereferencing the handle, we check if the handle's tag -matches the copy stored in the control block. this protects against stale -handle reuse, double-free, and accidentally referencing other resources. - -type: each handle has an associated type. these must be checked to prevent -using textures as sounds, for example. with the manual vtbl scheme, -this type is actually a pointer to the resource object's vtbl, and is -set up via H_TYPE_DEFINE. this means that types are private to the module -that declared the handle; knowledge of the type ensures the caller -actually declared, and owns the resource. - - -guide to defining and using resources -------------------------------------- - -1) choose a name for the resource, used to represent all resources -of this type. we will call ours "Res1"; all below occurrences of this -must be replaced with the actual name (exact spelling). -why? the vtbl builder defines its functions as e.g. Res1_reload; -your actual definition must match. - -2) declare its control block: -struct Res1 -{ - void* data; // data loaded from file - size_t flags; // set when resource is created -}; - -Note that all control blocks are stored in fixed-size slots -(HDATA_USER_SIZE bytes), so squeezing the size of your data doesn't -help unless yours is the largest. - -3) build its vtbl: -H_TYPE_DEFINE(Res1); - -this defines the symbol H_Res1, which is used whenever the handle -manager needs its type. it is only accessible to this module -(file scope). note that it is actually a pointer to the vtbl. -this must come before uses of H_Res1, and after the CB definition; -there are no restrictions WRT functions, because the macro -forward-declares what it needs. - -4) implement all 'virtual' functions from the resource interface. -note that inheritance isn't really possible with this approach - -all functions must be defined, even if not needed. - --- - -init: -one-time init of the control block. called from h_alloc. -precondition: control block is initialized to 0. - -static void Type_init(Res1* r, va_list args) -{ - r->flags = va_arg(args, int); -} - -if the caller of h_alloc passed additional args, they are available -in args. if init references more args than were passed, big trouble. -however, this is a bug in your code, and cannot be triggered -maliciously. only your code knows the resource type, and it is the -only call site of h_alloc. -there is no provision for indicating failure. if one-time init fails -(rare, but one example might be failure to allocate memory that is -for the lifetime of the resource, instead of in reload), it will -have to set the control block state such that reload will fail. - --- - -reload: -does all initialization of the resource that requires its source file. -called after init; also after dtor every time the file is reloaded. - -static Status Type_reload(Res1* r, const VfsPath& pathname, Handle); -{ - // already loaded; done - if(r->data) - return 0; - - r->data = malloc(100); - if(!r->data) - WARN_RETURN(ERR::NO_MEM); - // (read contents of into r->data) - return 0; -} - -reload must abort if the control block data indicates the resource -has already been loaded! example: if texture's reload is called first, -it loads itself from file (triggering file.reload); afterwards, -file.reload will be called again. we can't avoid this, because the -handle manager doesn't know anything about dependencies -(here, texture -> file). -return value: 0 if successful (includes 'already loaded'), -negative error code otherwise. if this fails, the resource is freed -(=> dtor is called!). - -note that any subsequent changes to the resource state must be -stored in the control block and 'replayed' when reloading. -example: when uploading a texture, store the upload parameters -(filter, internal format); when reloading, upload again accordingly. - --- - -dtor: -frees all data allocated by init and reload. called after reload has -indicated failure, before reloading a resource, after h_free, -or at exit (if the resource is still extant). -except when reloading, the control block will be zeroed afterwards. - -static void Type_dtor(Res1* r); -{ - free(r->data); -} - -again no provision for reporting errors - there's no one to act on it -if called at exit. you can ENSURE or log the error, though. - -be careful to correctly handle the different cases in which this routine -can be called! some flags should persist across reloads (e.g. choices made -during resource init time that must remain valid), while everything else -*should be zeroed manually* (to behave correctly when reloading). -be advised that this interface may change; a "prepare for reload" method -or "compact/free extraneous resources" may be added. - --- - -validate: -makes sure the resource control block is in a valid state. returns 0 if -all is well, or a negative error code. -called automatically when the Handle is dereferenced or freed. - -static Status Type_validate(const Res1* r); -{ - const int permissible_flags = 0x01; - if(debug_IsPointerBogus(r->data)) - WARN_RETURN(ERR::_1); - if(r->flags & ~permissible_flags) - WARN_RETURN(ERR::_2); - return 0; -} - - -5) provide your layer on top of the handle manager: -Handle res1_load(const VfsPath& pathname, int my_flags) -{ - // passes my_flags to init - return h_alloc(H_Res1, pathname, 0, my_flags); -} - -Status res1_free(Handle& h) -{ - // control block is automatically zeroed after this. - return h_free(h, H_Res1); -} - -(this layer allows a res_load interface on top of all the loaders, -and is necessary because your module is the only one that knows H_Res1). - -6) done. the resource will be freed at exit (if not done already). - -here's how to access the control block, given a : -a) - H_DEREF(h, Res1, r); - -creates a variable r of type Res1*, which points to the control block -of the resource referenced by h. returns "invalid handle" -(a negative error code) on failure. -b) - Res1* r = h_user_data(h, H_Res1); - if(!r) - ; // bail - -useful if H_DEREF's error return (of type signed integer) isn't -acceptable. otherwise, prefer a) - this is pretty clunky, and -we could switch H_DEREF to throwing an exception on error. - -*/ - -#ifndef INCLUDED_H_MGR -#define INCLUDED_H_MGR - -// do not include from public header files! -// handle.h declares type Handle, and avoids making -// everything dependent on this (rather often updated) header. - - -#include // type init routines get va_list of args - -#ifndef INCLUDED_HANDLE -#include "handle.h" -#endif - -#include "lib/file/vfs/vfs.h" - -extern void h_mgr_init(); -extern void h_mgr_shutdown(); - - -// handle type (for 'type safety' - can't use a texture handle as a sound) - -// registering extension for each module is bad - some may use many -// (e.g. texture - many formats). -// handle manager shouldn't know about handle types - - -/* -///xxx advantage of manual vtbl: -no boilerplate init, h_alloc calls ctor directly, make sure it fits in the memory slot -vtbl contains sizeof resource data, and name! -but- has to handle variable params, a bit ugly -*/ - -// 'manual vtbl' type id -// handles have a type, to prevent using e.g. texture handles as a sound. -// -// alternatives: -// - enum of all handle types (smaller, have to pass all methods to h_alloc) -// - class (difficult to compare type, handle manager needs to know of all users) -// -// checked in h_alloc: -// - user_size must fit in what the handle manager provides -// - name must not be 0 -// -// init: user data is initially zeroed -// dtor: user data is zeroed afterwards -// reload: if this resource type is opened by another resource's reload, -// our reload routine MUST check if already opened! This is relevant when -// a file is reloaded: if e.g. a sound object opens a file, the handle -// manager calls the reload routines for the 2 handles in unspecified order. -// ensuring the order would require a tag field that can't overflow - -// not really guaranteed with 32-bit handles. it'd also be more work -// to sort the handles by creation time, or account for several layers of -// dependencies. -struct H_VTbl -{ - void (*init)(void* user, va_list); - Status (*reload)(void* user, const PIVFS& vfs, const VfsPath& pathname, Handle); - void (*dtor)(void* user); - Status (*validate)(const void* user); - Status (*to_string)(const void* user, wchar_t* buf); - size_t user_size; - const wchar_t* name; -}; - -typedef H_VTbl* H_Type; - -#define H_TYPE_DEFINE(type)\ - /* forward decls */\ - static void type##_init(type*, va_list);\ - static Status type##_reload(type*, const PIVFS&, const VfsPath&, Handle);\ - static void type##_dtor(type*);\ - static Status type##_validate(const type*);\ - static Status type##_to_string(const type*, wchar_t* buf);\ - static H_VTbl V_##type =\ - {\ - (void (*)(void*, va_list))type##_init,\ - (Status (*)(void*, const PIVFS&, const VfsPath&, Handle))type##_reload,\ - (void (*)(void*))type##_dtor,\ - (Status (*)(const void*))type##_validate,\ - (Status (*)(const void*, wchar_t*))type##_to_string,\ - sizeof(type), /* control block size */\ - WIDEN(#type) /* name */\ - };\ - static H_Type H_##type = &V_##type - - // note: we cast to void* pointers so the functions can be declared to - // take the control block pointers, instead of requiring a cast in each. - // the forward decls ensure the function signatures are correct. - - -// convenience macro for h_user_data: -// casts its return value to the control block type. -// use if H_DEREF's returning a negative error code isn't acceptable. -#define H_USER_DATA(h, type) (type*)h_user_data(h, H_##type) - -// even more convenient wrapper for h_user_data: -// declares a pointer (), assigns it H_USER_DATA, and has -// the user's function return a negative error code on failure. -// -// note: don't use STMT - var decl must be visible to "caller" -#define H_DEREF(h, type, var)\ - /* h already indicates an error - return immediately to pass back*/\ - /* that specific error, rather than only ERR::INVALID_HANDLE*/\ - if(h < 0)\ - WARN_RETURN((Status)h);\ - type* const var = H_USER_DATA(h, type);\ - if(!var)\ - WARN_RETURN(ERR::INVALID_HANDLE); - - -// all functions check the passed tag (part of the handle) and type against -// the internal values. if they differ, an error is returned. - - - - -// h_alloc flags -enum -{ - // alias for RES_TEMP scope. the handle will not be kept open. - RES_NO_CACHE = 0x01, - - // not cached, and will never reuse a previous instance - RES_UNIQUE = RES_NO_CACHE|0x10, - - // object is requesting it never be reloaded (e.g. because it's not - // backed by a file) - RES_DISALLOW_RELOAD = 0x20 -}; - -const size_t H_STRING_LEN = 256; - - - -// allocate a new handle. -// if key is 0, or a (key, type) handle doesn't exist, -// some free entry is used. -// otherwise, a handle to the existing object is returned, -// and HDATA.size != 0. -//// user_size is checked to make sure the user data fits in the handle data space. -// dtor is associated with type and called when the object is freed. -// handle data is initialized to 0; optionally, a pointer to it is returned. -extern Handle h_alloc(H_Type type, const PIVFS& vfs, const VfsPath& pathname, size_t flags = 0, ...); -extern Status h_free(Handle& h, H_Type type); - - -// Forcibly frees all handles of a specified type. -void h_mgr_free_type(const H_Type type); - - -// find and return a handle by key (typically filename hash) -// currently O(log n). -// -// HACK: currently can't find RES_UNIQUE handles, because there -// may be multiple instances of them, breaking the lookup data structure. -extern Handle h_find(H_Type type, uintptr_t key); - -// returns a void* pointer to the control block of the resource , -// or 0 on error (i.e. h is invalid or of the wrong type). -// prefer using H_DEREF or H_USER_DATA. -extern void* h_user_data(Handle h, H_Type type); - -extern VfsPath h_filename(Handle h); - - -extern Status h_reload(const PIVFS& vfs, const VfsPath& pathname); - -// force the resource to be freed immediately, even if cached. -// tag is not checked - this allows the first Handle returned -// (whose tag will change after being 'freed', but remaining in memory) -// to later close the object. -// this is used when reinitializing the sound engine - -// at that point, all (cached) OpenAL resources must be freed. -extern Status h_force_free(Handle h, H_Type type); - -// increment Handle 's reference count. -// only meant to be used for objects that free a Handle in their dtor, -// so that they are copy-equivalent and can be stored in a STL container. -// do not use this to implement refcounting on top of the Handle scheme, -// e.g. loading a Handle once and then passing it around. instead, have each -// user load the resource; refcounting is done under the hood. -extern void h_add_ref(Handle h); - -// retrieve the internal reference count or a negative error code. -// background: since h_alloc has no way of indicating whether it -// allocated a new handle or reused an existing one, counting references -// within resource control blocks is impossible. since that is sometimes -// necessary (always wrapping objects in Handles is excessive), we -// provide access to the internal reference count. -extern intptr_t h_get_refcnt(Handle h); - -#endif // #ifndef INCLUDED_H_MGR diff --git a/source/lib/res/handle.h b/source/lib/res/handle.h deleted file mode 100644 index 401e6a7d95..0000000000 --- a/source/lib/res/handle.h +++ /dev/null @@ -1,43 +0,0 @@ -/* Copyright (C) 2010 Wildfire Games. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * forward declaration of Handle (reduces dependencies) - */ - -#ifndef INCLUDED_HANDLE -#define INCLUDED_HANDLE - -/** - * `handle' representing a reference to a resource (sound, texture, etc.) - * - * 0 is the (silently ignored) invalid handle value; < 0 is an error code. - * - * this is 64 bits because we want tags to remain unique. (tags are a - * counter that disambiguate several subsequent uses of the same - * resource array slot). 32-bit handles aren't enough because the index - * field requires at least 12 bits, thus leaving only about 512K possible - * tag values. - **/ -typedef i64 Handle; - -#endif // #ifndef INCLUDED_HANDLE diff --git a/source/lib/tex/tex.h b/source/lib/tex/tex.h index 6a81143a87..bffd481dad 100644 --- a/source/lib/tex/tex.h +++ b/source/lib/tex/tex.h @@ -104,7 +104,6 @@ library and IO layer. Read and write are zero-copy. #ifndef INCLUDED_TEX #define INCLUDED_TEX -#include "lib/res/handle.h" #include "lib/os_path.h" #include "lib/file/vfs/vfs_path.h" #include "lib/allocators/dynarray.h" diff --git a/source/ps/Filesystem.cpp b/source/ps/Filesystem.cpp index 5ce024417d..bc60c5bb19 100644 --- a/source/ps/Filesystem.cpp +++ b/source/ps/Filesystem.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2022 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -16,16 +16,15 @@ */ #include "precompiled.h" + #include "Filesystem.h" +#include "lib/sysdep/dir_watch.h" +#include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Profile.h" -#include "lib/res/h_mgr.h" // h_reload -#include "lib/sysdep/dir_watch.h" -#include "lib/utf8.h" - #include PIVFS g_VFS; @@ -93,8 +92,6 @@ Status ReloadChangedFiles() for (size_t j = 0; j < g_ReloadFuncs.size(); ++j) g_ReloadFuncs[j].first(g_ReloadFuncs[j].second, pathname); - - RETURN_STATUS_IF_ERR(h_reload(g_VFS, pathname)); } } return INFO::OK; diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index d1bb942a8e..1aeb7a7d3e 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -30,8 +30,6 @@ #include "lib/external_libraries/libsdl.h" #include "lib/file/common/file_stats.h" #include "lib/input.h" -#include "lib/ogl.h" -#include "lib/res/h_mgr.h" #include "lib/timer.h" #include "lobby/IXmppClient.h" #include "network/NetServer.h" @@ -422,10 +420,6 @@ from_config: g_VFS.reset(); - // this forcibly frees all open handles (thus preventing real leaks), - // and makes further access to h_mgr impossible. - h_mgr_shutdown(); - file_stats_dump(); TIMER_END(L"resource modules"); @@ -551,8 +545,6 @@ bool AutostartVisualReplay(const std::string& replayFile); bool Init(const CmdLineArgs& args, int flags) { - h_mgr_init(); - // Do this as soon as possible, because it chdirs // and will mess up the error reporting if anything // crashes before the working directory is set. diff --git a/source/ps/Replay.cpp b/source/ps/Replay.cpp index 077be8d8fb..b15ba07259 100644 --- a/source/ps/Replay.cpp +++ b/source/ps/Replay.cpp @@ -22,7 +22,6 @@ #include "graphics/TerrainTextureManager.h" #include "lib/timer.h" #include "lib/file/file_system.h" -#include "lib/res/h_mgr.h" #include "lib/tex/tex.h" #include "ps/CLogger.h" #include "ps/Game.h" @@ -248,9 +247,6 @@ void CReplayPlayer::Replay(const bool serializationtest, const int rejointesttur if (ooslog) g_Game->GetSimulation2()->EnableOOSLog(); - // Initialise h_mgr so it doesn't crash when emitting sounds - h_mgr_init(); - ScriptRequest rq(g_Game->GetSimulation2()->GetScriptInterface()); JS::RootedValue attribs(rq.cx); ENSURE(Script::ParseJSON(rq, attribsStr, &attribs)); -- 2.11.4.GIT