Merge pull request #2178 from KLayout/feature/issue-2168

Feature/issue 2168
This commit is contained in:
Matthias Köfferlein 2025-10-18 13:51:25 +02:00 committed by GitHub
commit 8cc01ca814
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 446 additions and 44 deletions

View File

@ -145,9 +145,20 @@ Library::is_retired (const db::cell_index_type library_cell_index) const
return (i != m_refcount.end () && j != m_retired_count.end () && i->second == j->second);
}
void
Library::rename (const std::string &name)
{
if (name != get_name () && db::LibraryManager::initialized ()) {
db::LibraryManager::instance ().rename (get_id (), name);
}
}
void
Library::refresh ()
{
std::string name = reload ();
rename (name);
layout ().refresh ();
remap_to (this);
}
@ -271,6 +282,9 @@ Library::remap_to (db::Library *other)
// Do a cleanup later since the referrers now might have invalid proxy instances
for (std::set<db::Layout *>::const_iterator c = needs_cleanup.begin (); c != needs_cleanup.end (); ++c) {
(*c)->cleanup ();
// forces an update of the cell tree in the application - this will reflect the changed name
// of the library reference
(*c)->invalidate_hier ();
}
}

View File

@ -65,6 +65,20 @@ public:
*/
virtual ~Library ();
/**
* @brief Called to reload the library
*
* If the library is a file-based one, this method can be reimplemented to reload
* the file. This method must not change the name of the library, but return a new
* name in case it has changed.
*
* @return The new name of the library
*/
virtual std::string reload ()
{
return get_name ();
}
/**
* @brief The layout object
*
@ -211,6 +225,14 @@ public:
*/
void refresh ();
/**
* @brief Renames the library
*
* Unlike "set_name", this method will take care of properly re-registering the library
* under the new name.
*/
void rename (const std::string &name);
/**
* @brief Remap the library proxies to a different library
*

View File

@ -67,6 +67,39 @@ LibraryManager::~LibraryManager ()
clear ();
}
void
LibraryManager::rename (lib_id_type lib_id, const std::string &name)
{
db::Library *lib = 0;
{
tl::MutexLocker locker (&m_lock);
lib = lib_internal (lib_id);
if (! lib) {
return;
}
std::string org_name = lib->get_name ();
for (auto it = m_lib_by_name.find (org_name);it != m_lib_by_name.end () && it->first == org_name; ++it) {
if (it->second == lib_id) {
m_lib_by_name.erase (it);
break;
}
}
m_lib_by_name.insert (std::make_pair (name, lib_id));
lib->set_name (name);
}
// triggers a layout update
lib->remap_to (lib);
// issue the change notification
changed_event ();
}
std::pair<bool, lib_id_type>
LibraryManager::lib_by_name (const std::string &name, const std::set<std::string> &for_technologies) const
{
@ -112,6 +145,8 @@ LibraryManager::unregister_lib (Library *library)
return;
}
library->remap_to (0);
{
tl::MutexLocker locker (&m_lock);
@ -124,8 +159,10 @@ LibraryManager::unregister_lib (Library *library)
}
}
library->remap_to (0);
library->set_id (std::numeric_limits<lib_id_type>::max ());
// issue the change notification
changed_event ();
}
void
@ -244,6 +281,16 @@ LibraryManager::lib_internal (lib_id_type id) const
}
}
void
LibraryManager::refresh_all ()
{
for (std::vector<Library *>::iterator l = m_libs.begin (); l != m_libs.end (); ++l) {
if (*l) {
(*l)->refresh ();
}
}
}
void
LibraryManager::clear ()
{

View File

@ -91,6 +91,11 @@ public:
return m_lib_by_name.end ();
}
/**
* @brief Renames a library
*/
void rename (lib_id_type lib_id, const std::string &name);
/**
* @brief Get the library by name which is valid for all given technologies
*
@ -212,6 +217,11 @@ public:
*/
void clear ();
/**
* @brief Refreshes all libraries
*/
void refresh_all ();
private:
std::vector<Library *> m_libs;
lib_name_map m_lib_by_name;

View File

@ -974,6 +974,45 @@ static db::Library *library (const db::Cell *cell)
}
}
static void change_library_ref (db::Cell *cell, db::lib_id_type lib_id, db::cell_index_type cell_index)
{
db::LibraryProxy *l = dynamic_cast<db::LibraryProxy *> (cell);
if (! l) {
throw tl::Exception (tl::to_string (tr ("Cell is not a library reference - cannot change that reference")));
}
const db::Library *lib = db::LibraryManager::instance ().lib (lib_id);
if (! lib) {
throw tl::Exception (tl::to_string (tr ("'lib_id' is not a valid library ID")));
}
if (! lib->layout ().is_valid_cell_index (cell_index)) {
throw tl::Exception (tl::to_string (tr ("'cell_index' is not a valid cell index in the context of the library")));
}
l->remap (lib_id, cell_index);
}
static void change_library_ref2 (db::Cell *cell, const std::string &lib_name, const std::string &cell_name)
{
db::LibraryProxy *l = dynamic_cast<db::LibraryProxy *> (cell);
if (! l) {
throw tl::Exception (tl::to_string (tr ("Cell is not a library reference - cannot change that reference")));
}
db::Library *lib = db::LibraryManager::instance ().lib_ptr_by_name (lib_name);
if (! lib) {
throw tl::Exception (tl::to_string (tr ("Not a valid library name: ")) + lib_name);
}
auto cbn = lib->layout ().cell_by_name (cell_name.c_str ());
if (! cbn.first) {
throw tl::Exception (tl::to_string (tr ("Not a valid cell name: ")) + cell_name);
}
l->remap (lib->get_id (), cbn.second);
}
static const db::Layout *layout_const (const db::Cell *cell)
{
return cell->layout ();
@ -3194,8 +3233,24 @@ Class<db::Cell> decl_Cell ("db", "Cell",
"@brief Returns a reference to the library from which the cell is imported\n"
"if the cell is not imported from a library, this reference is nil.\n"
"\n"
"this method has been introduced in version 0.22.\n"
"This method has been introduced in version 0.22.\n"
) +
gsi::method_ext ("change_ref", &change_library_ref, gsi::arg ("lib_id"), gsi::arg ("lib_cell_index"),
"@brief Changes the reference to a different library cell\n"
"This method requires a cell that is a library reference (i.e. \\is_library_cell? is true). It will "
"change that reference to a new cell, potentially from a different library.\n"
"The library is given by library ID, the cell by cell inside inside that library.\n"
"\n"
"This method has been introduced in version 0.30.5.\n"
) +
gsi::method_ext ("change_ref", &change_library_ref2, gsi::arg ("lib_name"), gsi::arg ("cell_name"),
"@brief Changes the reference to a different library cell\n"
"This method requires a cell that is a library reference (i.e. \\is_library_cell? is true). It will "
"change that reference to a new cell, potentially from a different library.\n"
"This version takes a library name and cell name (from that library).\n"
"\n"
"This method has been introduced in version 0.30.5.\n"
) +
gsi::method_ext ("layout", &layout,
"@brief Returns a reference to the layout where the cell resides\n"
"\n"

View File

@ -37,14 +37,6 @@ namespace gsi
// ---------------------------------------------------------------
// db::Library binding
/**
* @brief A basic implementation of the library
*/
static db::Library *new_lib ()
{
return new db::Library ();
}
static db::Library *library_by_name (const std::string &name, const std::string &for_technology)
{
return db::LibraryManager::instance ().lib_ptr_by_name (name, for_technology);
@ -73,12 +65,22 @@ static std::vector<db::lib_id_type> library_ids ()
return r;
}
static void refresh_all ()
{
db::LibraryManager::instance ().refresh_all ();
}
static void register_lib (db::Library *lib, const std::string &name)
{
lib->set_name (name);
db::LibraryManager::instance ().register_lib (lib);
}
static void unregister_lib (db::Library *lib)
{
db::LibraryManager::instance ().unregister_lib (lib);
}
static void delete_lib (db::Library *lib)
{
db::LibraryManager::instance ().delete_lib (lib);
@ -97,7 +99,7 @@ static std::string get_technology (db::Library *lib)
static void destroy_lib (db::Library *lib)
{
if (db::LibraryManager::instance ().lib_ptr_by_name (lib->get_name ()) == lib) {
// Library is registered -> do not delete
delete_lib (lib);
} else {
delete lib;
}
@ -105,31 +107,63 @@ static void destroy_lib (db::Library *lib)
namespace {
template <class Base>
class LibraryClass
: public Class<db::Library>
: public gsi::Class<Base>
{
public:
LibraryClass (const char *module, const char *name, const gsi::Methods &methods, const char *description)
: Class<db::Library> (module, name, methods, description)
: gsi::Class<Base> (module, name, methods, description)
{ }
template <class B>
LibraryClass (const gsi::Class<B> &base, const char *module, const char *name, const gsi::Methods &methods, const char *description)
: gsi::Class<Base> (base, module, name, methods, description)
{ }
virtual void destroy (void *p) const
{
db::Library *lib = reinterpret_cast<db::Library *> (p);
Base *lib = reinterpret_cast<Base *> (p);
destroy_lib (lib);
}
};
class LibraryImpl
: public db::Library
{
public:
LibraryImpl () : db::Library ()
{
// .. nothing yet ..
}
virtual std::string reload ()
{
if (cb_reload.can_issue ()) {
return cb_reload.issue<db::Library, std::string> (&db::Library::reload);
} else {
return db::Library::reload ();
}
}
gsi::Callback cb_reload;
};
}
LibraryClass decl_Library ("db", "Library",
gsi::constructor ("new", &new_lib,
"@brief Creates a new, empty library"
) +
static LibraryImpl *new_lib ()
{
return new LibraryImpl ();
}
/**
* @brief A basic implementation of the library
*/
LibraryClass<db::Library> decl_Library ("db", "LibraryBase",
gsi::method ("library_by_name", &library_by_name, gsi::arg ("name"), gsi::arg ("for_technology", std::string (), "unspecific"),
"@brief Gets a library by name\n"
"Returns the library object for the given name. If the name is not a valid\n"
"library name, nil is returned.\n"
"Returns the library object for the given name. If the name is not a valid library name, nil is returned.\n"
"\n"
"Different libraries can be registered under the same names for different technologies. When a technology name is given in 'for_technologies', "
"the first library matching this technology is returned. If no technology is given, the first library is returned.\n"
@ -155,6 +189,11 @@ LibraryClass decl_Library ("db", "Library",
"\n"
"This method has been introduced in version 0.27."
) +
gsi::method ("refresh_all", &refresh_all,
"@brief Calls \\refresh on all libraries.\n"
"\n"
"This convenience method has been introduced in version 0.30.4."
) +
gsi::method_ext ("register", &register_lib, gsi::arg ("name"),
"@brief Registers the library with the given name\n"
"\n"
@ -166,6 +205,22 @@ LibraryClass decl_Library ("db", "Library",
"\n"
"The technology specific behaviour has been introduced in version 0.27."
) +
gsi::method_ext ("unregister", &unregister_lib,
"@brief Unregisters the library\n"
"\n"
"Unregisters the library from the system. This will break all references of cells "
"using this library and make them 'defunct'.\n"
"\n"
"This method has been introduced in version 0.30.5."
) +
gsi::method ("rename", &db::Library::rename, gsi::arg ("name"),
"@brief Renames the library\n"
"\n"
"Re-registers the library under a new name. Note that this method will also change the references "
"to the library.\n"
"\n"
"This method has been introduced in version 0.30.5."
) +
gsi::method_ext ("delete", &delete_lib,
"@brief Deletes the library\n"
"\n"
@ -176,7 +231,7 @@ LibraryClass decl_Library ("db", "Library",
) +
gsi::method ("name", &db::Library::get_name,
"@brief Returns the libraries' name\n"
"The name is set when the library is registered and cannot be changed\n"
"The name is set when the library is registered. To change it use \\rename.\n"
) +
gsi::method ("id", &db::Library::get_id,
"@brief Returns the library's ID\n"
@ -240,9 +295,32 @@ LibraryClass decl_Library ("db", "Library",
"@brief Updates all layouts using this library.\n"
"This method will retire cells or update layouts in the attached clients.\n"
"It will also recompute the PCells inside the library. "
"Starting with version 0.30.5, this method will also call 'reload' on all libraries to "
"refresh cells located in external files.\n"
"\n"
"This method has been introduced in version 0.27.8."
),
"@hide"
);
/**
* @brief The reimplementation stub
*/
LibraryClass<LibraryImpl> decl_LibraryImpl (decl_Library, "db", "Library",
gsi::constructor ("new", &new_lib,
"@brief Creates a new, empty library"
) +
gsi::callback ("reload", &LibraryImpl::reload, &LibraryImpl::cb_reload,
"@brief Reloads resources for the library.\n"
"Reimplement this method if you like to reload resources the library was created from - "
"for example layout files. Make sure you return the new name of the library from this function. "
"If you do not want to change the name of the library, return the current name (i.e. the value of \\name).\n"
"\n"
"@return The new name of the library or the original name if it did not change.\n"
"\n"
"This method is called on \\refresh. It was introduced in version 0.30.5.\n"
),
"@brief A Library \n"
"\n"
"A library is basically a wrapper around a layout object. The layout object\n"

View File

@ -1034,7 +1034,7 @@ Class<db::EqualDeviceParameters> decl_dbEqualDeviceParameters ("db", "EqualDevic
Class<GenericDeviceParameterCompare> decl_GenericDeviceParameterCompare (decl_dbEqualDeviceParameters, "db", "GenericDeviceParameterCompare",
gsi::callback ("less", &GenericDeviceParameterCompare::less, &GenericDeviceParameterCompare::cb_less, gsi::arg ("device_a"), gsi::arg ("device_b"),
"@brief Compares the parameters of two devices for a begin less than b. "
"@brief Compares the parameters of two devices for a begin less than b.\n"
"Returns true, if the parameters of device a are considered less than those of device b."
"The 'less' implementation needs to ensure strict weak ordering. Specifically, less(a,b) == false and less(b,a) implies that a is equal to b and "
"less(a,b) == true implies that less(b,a) is false and vice versa. If not, an internal error "

View File

@ -73,6 +73,7 @@ static const char *cm_symbols[] = {
"cm_save",
"cm_save_all",
"cm_reload",
"cm_refresh",
"cm_close",
"cm_close_all",
"cm_clone",

View File

@ -32,12 +32,53 @@
#include "dbReader.h"
#include "tlLog.h"
#include "tlStream.h"
#include "tlFileUtils.h"
#include <QDir>
namespace lay
{
// -------------------------------------------------------------------------------------------
class FileBasedLibrary
: public db::Library
{
public:
FileBasedLibrary (const std::string &path)
: db::Library (), m_path (path)
{
set_description (tl::filename (path));
}
virtual std::string reload ()
{
std::string name = tl::basename (m_path);
layout ().clear ();
tl::InputStream stream (m_path);
db::Reader reader (stream);
reader.read (layout ());
// Use the libname if there is one
db::Layout::meta_info_name_id_type libname_name_id = layout ().meta_info_name_id ("libname");
for (db::Layout::meta_info_iterator m = layout ().begin_meta (); m != layout ().end_meta (); ++m) {
if (m->first == libname_name_id && ! m->second.value.is_nil ()) {
name = m->second.value.to_string ();
break;
}
}
return name;
}
private:
std::string m_path;
};
// -------------------------------------------------------------------------------------------
LibraryController::LibraryController ()
: m_file_watcher (0),
dm_sync_files (this, &LibraryController::sync_files)
@ -166,7 +207,6 @@ LibraryController::sync_files ()
QStringList libs = lp.entryList (name_filters, QDir::Files);
for (QStringList::const_iterator im = libs.begin (); im != libs.end (); ++im) {
std::string filename = tl::to_string (*im);
std::string lib_path = tl::to_string (lp.absoluteFilePath (*im));
try {
@ -187,26 +227,13 @@ LibraryController::sync_files ()
if (needs_load) {
std::unique_ptr<db::Library> lib (new db::Library ());
lib->set_description (filename);
std::unique_ptr<db::Library> lib (new FileBasedLibrary (lib_path));
if (! p->second.empty ()) {
lib->set_technology (p->second);
}
lib->set_name (tl::to_string (QFileInfo (*im).baseName ()));
tl::log << "Reading library '" << lib_path << "'";
tl::InputStream stream (lib_path);
db::Reader reader (stream);
reader.read (lib->layout ());
// Use the libname if there is one
db::Layout::meta_info_name_id_type libname_name_id = lib->layout ().meta_info_name_id ("libname");
for (db::Layout::meta_info_iterator m = lib->layout ().begin_meta (); m != lib->layout ().end_meta (); ++m) {
if (m->first == libname_name_id && ! m->second.value.is_nil ()) {
lib->set_name (m->second.value.to_string ());
break;
}
}
lib->set_name (lib->reload ());
if (! p->second.empty ()) {
tl::log << "Registering as '" << lib->get_name () << "' for tech '" << p->second << "'";

View File

@ -2555,6 +2555,12 @@ MainWindow::cm_writer_options ()
mp_layout_save_options->edit_global_options (dispatcher (), db::Technologies::instance ());
}
void
MainWindow::cm_refresh ()
{
db::LibraryManager::instance ().refresh_all ();
}
void
MainWindow::cm_new_panel ()
{
@ -4036,6 +4042,8 @@ MainWindow::menu_activated (const std::string &symbol)
cm_reader_options ();
} else if (symbol == "cm_writer_options") {
cm_writer_options ();
} else if (symbol == "cm_refresh") {
cm_refresh ();
} else if (symbol == "cm_new_panel") {
cm_new_panel ();
} else if (symbol == "cm_new_layout") {
@ -4449,6 +4457,7 @@ public:
menu_entries.push_back (lay::menu_item ("cm_close_all", "close_all:edit", at, tl::to_string (QObject::tr ("Close All(Shift+Ctrl+W)"))));
menu_entries.push_back (lay::menu_item ("cm_clone", "clone", at, tl::to_string (QObject::tr ("Clone Panel"))));
menu_entries.push_back (lay::menu_item ("cm_reload", "reload:edit", at, tl::to_string (QObject::tr ("Reload(Ctrl+R)"))));
menu_entries.push_back (lay::menu_item ("cm_refresh", "refresh:edit", at, tl::to_string (QObject::tr ("Refresh Libraries"))));
menu_entries.push_back (lay::menu_item ("cm_pull_in", "pull_in:edit", at, tl::to_string (QObject::tr ("Pull In Other Layout"))));
menu_entries.push_back (lay::menu_item ("cm_reader_options", "reader_options", at, tl::to_string (QObject::tr ("Reader Options"))));
menu_entries.push_back (lay::separator ("open_recent_group", at));

View File

@ -837,6 +837,7 @@ private:
void cm_pull_in ();
void cm_reader_options ();
void cm_writer_options ();
void cm_refresh ();
void cm_new_panel ();
void cm_new_layout ();
void cm_clone ();

View File

@ -23,6 +23,19 @@ end
load("test_prologue.rb")
class MyLibImpl < RBA::Library
def initialize
@reload_count = 0
end
def reload_count
@reload_count
end
def reload
@reload_count += 1
return "RBA-unit-test2"
end
end
class DBLibrary_TestClass < TestBase
def test_1_registration
@ -45,14 +58,17 @@ class DBLibrary_TestClass < TestBase
assert_equal(RBA::Library::library_names.member?("RBA-unit-test"), true)
assert_equal(RBA::Library::library_by_name("RBA-unit-test").id, lib_id)
# destroy should not do anything as libraries are not to be removed through the destructor
lib._destroy
assert_equal(RBA::Library::library_by_name("RBA-unit-test").id, lib_id)
assert_equal(lib.destroyed?, true)
# The library reference is kept internally
lib = nil
GC.start
GC.start
lib = RBA::Library::library_by_name("RBA-unit-test")
assert_equal(lib.destroyed?, false)
lib.delete
assert_equal(lib.name, "RBA-unit-test")
lib._destroy
assert_equal(lib.destroyed?, true)
assert_equal(RBA::Library::library_by_name("RBA-unit-test"), nil)
end
@ -103,6 +119,128 @@ class DBLibrary_TestClass < TestBase
end
def test_4_library_registration_and_rename
lib = RBA::Library::new
lib.description = "LIB1"
lib.delete
assert_equal(lib.destroyed?, true)
lib = RBA::Library::new
lib.layout.create_cell("A")
lib.description = "LIB1"
lib.register("RBA-unit-test")
assert_equal(RBA::Library::library_by_name("RBA-unit-test").description, "LIB1")
lib.unregister
assert_equal(lib.destroyed?, false)
assert_equal(RBA::Library::library_by_name("RBA-unit-test"), nil)
lib.register("RBA-unit-test")
assert_equal(RBA::Library::library_by_name("RBA-unit-test").description, "LIB1")
ly = RBA::Layout::new
ci = ly.create_cell("A", "RBA-unit-test").cell_index
assert_equal(ly.cell(ci).qname, "RBA-unit-test.A")
lib.rename("RBA-unit-test2")
assert_equal(RBA::Library::library_by_name("RBA-unit-test"), nil)
assert_equal(RBA::Library::library_by_name("RBA-unit-test2").description, "LIB1")
assert_equal(ly.cell(ci).qname, "RBA-unit-test2.A")
lib.delete
assert_equal(RBA::Library::library_by_name("RBA-unit-test"), nil)
assert_equal(RBA::Library::library_by_name("RBA-unit-test2"), nil)
assert_equal(ly.cell(ci).qname, "<defunct>RBA-unit-test2.A")
end
def test_5_reload
lib = MyLibImpl::new
lib.description = "LIB1"
lib.register("RBA-unit-test")
assert_equal(RBA::Library::library_by_name("RBA-unit-test").description, "LIB1")
lib.refresh
assert_equal(RBA::Library::library_by_name("RBA-unit-test"), nil)
assert_equal(RBA::Library::library_by_name("RBA-unit-test2").description, "LIB1")
assert_equal(lib.reload_count, 1)
lib.delete
assert_equal(RBA::Library::library_by_name("RBA-unit-test"), nil)
assert_equal(RBA::Library::library_by_name("RBA-unit-test2"), nil)
end
def test_6_cells_become_defunct_after_unregister
lib = RBA::Library::new
lib.description = "LIB1"
lib.register("RBA-unit-test")
cell_a = lib.layout.create_cell("A")
l1 = lib.layout.layer(1, 0)
cell_a.shapes(l1).insert(RBA::Box::new(0, 0, 1000, 2000))
ly = RBA::Layout::new
lc = ly.create_cell("A", "RBA-unit-test")
assert_equal(lc.qname, "RBA-unit-test.A")
ci = lc.cell_index
lib.unregister
# NOTE: cleanup has not been called, so we can find the cell using the cell_index
# (the actual cell object is no longer there because it has been replaced by
# a cold proxy)
lc = ly.cell(ci)
assert_equal(lc.qname, "<defunct>RBA-unit-test.A")
# this will restore the reference
lib.register("RBA-unit-test")
lc = ly.cell(ci)
assert_equal(lc.qname, "RBA-unit-test.A")
end
def test_7_change_ref
lib = RBA::Library::new
lib.description = "LIB1"
lib.register("RBA-unit-test")
l1 = lib.layout.layer(1, 0)
cell_a = lib.layout.create_cell("A")
cell_a.shapes(l1).insert(RBA::Box::new(0, 0, 1000, 2000))
lib2 = RBA::Library::new
lib2.description = "LIB2"
lib2.register("RBA-unit-test2")
l1 = lib2.layout.layer(1, 0)
cell_b = lib2.layout.create_cell("B")
cell_b.shapes(l1).insert(RBA::Box::new(0, 0, 2000, 1000))
ly = RBA::Layout::new
c1 = ly.create_cell("A", "RBA-unit-test")
assert_equal(c1.qname, "RBA-unit-test.A")
c1.change_ref(lib2.id, cell_b.cell_index)
assert_equal(c1.qname, "RBA-unit-test2.B")
ly = RBA::Layout::new
c1 = ly.create_cell("A", "RBA-unit-test")
assert_equal(c1.qname, "RBA-unit-test.A")
c1.change_ref("RBA-unit-test2", "B")
assert_equal(c1.qname, "RBA-unit-test2.B")
end
end
load("test_epilogue.rb")