Merge pull request #2280 from KLayout/wip

Wip
This commit is contained in:
Matthias Köfferlein 2026-03-04 18:46:10 +01:00 committed by GitHub
commit e82d2140b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 1218 additions and 813 deletions

View File

@ -41,6 +41,7 @@ HAVE_CURL=0
HAVE_EXPAT=0
HAVE_GIT2=1
HAVE_LSTREAM=1
HAVE_CPP20=0
RUBYINCLUDE=""
RUBYINCLUDE2=""
@ -217,6 +218,9 @@ while [ "$*" != "" ]; do
-nolstream)
HAVE_LSTREAM=0
;;
-cpp20)
HAVE_CPP20=1
;;
-qt5)
echo "*** WARNING: -qt5 option is ignored - Qt version is auto-detected now."
;;
@ -275,6 +279,7 @@ while [ "$*" != "" ]; do
echo " -libpng Use libpng instead of Qt for PNG generation"
echo " -nolibgit2 Do not include libgit2 for Git package support"
echo " -nolstream Do not include the LStream plugin"
echo " -cpp20 Uses some C++20 features (e.g. atomics)"
echo ""
echo "Environment Variables:"
echo ""
@ -670,6 +675,7 @@ qmake_options=(
HAVE_PNG="$HAVE_PNG"
HAVE_GIT2="$HAVE_GIT2"
HAVE_LSTREAM="$HAVE_LSTREAM"
HAVE_CPP20="$HAVE_CPP20"
PREFIX="$BIN"
RPATH="$RPATH"
KLAYOUT_VERSION="$KLAYOUT_VERSION"

View File

@ -98,6 +98,20 @@ const std::string GenericWriterOptions::dxf_format_name = "DXF";
const std::string GenericWriterOptions::cif_format_name = "CIF";
const std::string GenericWriterOptions::mag_format_name = "MAG";
std::vector<std::string>
GenericWriterOptions::all_format_names ()
{
std::vector<std::string> names;
names.push_back (gds2_format_name);
names.push_back (gds2text_format_name);
names.push_back (oasis_format_name);
names.push_back (lstream_format_name);
names.push_back (dxf_format_name);
names.push_back (cif_format_name);
names.push_back (mag_format_name);
return names;
}
void
GenericWriterOptions::add_options (tl::CommandLineOptions &cmd, const std::string &format)
{
@ -109,6 +123,15 @@ GenericWriterOptions::add_options (tl::CommandLineOptions &cmd, const std::strin
"given factor."
);
if (format.empty ()) {
cmd << tl::arg (group +
"-of|--format=format", &m_format, "Specifies the output format",
"By default, the output format is derived from the file name suffix. "
"You can also specify the format directly using this option. Allowed format names are: "
+ tl::join (all_format_names (), ", ")
);
}
if (format.empty () || format == gds2_format_name || format == gds2text_format_name || format == oasis_format_name) {
cmd << tl::arg (group +
"-od|--dbu-out=dbu", &m_dbu, "Uses the specified database unit",
@ -426,6 +449,22 @@ GenericWriterOptions::configure (db::SaveLayoutOptions &save_options, const db::
save_options.set_keep_instances (m_keep_instances);
save_options.set_write_context_info (m_write_context_info);
if (! m_format.empty ()) {
// check, if the format name is a valid one
std::vector<std::string> af = all_format_names ();
auto i = af.begin ();
while (i != af.end () && *i != m_format) {
++i;
}
if (i == af.end ()) {
throw tl::Exception (tl::sprintf (tl::to_string (tr ("Invalid fornat name %s. Allowed names are: %s")), m_format, tl::join (af, ", ")));
}
save_options.set_format (m_format);
}
save_options.set_option_by_name ("gds2_max_vertex_count", m_gds2_max_vertex_count);
save_options.set_option_by_name ("gds2_no_zero_length_paths", m_gds2_no_zero_length_paths);
save_options.set_option_by_name ("gds2_multi_xy_records", m_gds2_multi_xy_records);

View File

@ -26,6 +26,7 @@
#include "bdCommon.h"
#include <string>
#include <vector>
namespace tl
{
@ -60,6 +61,11 @@ public:
*/
GenericWriterOptions (const db::SaveLayoutOptions &options);
/**
* @brief Gets a list with all format names available
*/
static std::vector<std::string> all_format_names ();
/**
* @brief Adds the generic options to the command line parser object
* The format string gives a hint about the target format. Certain options will be
@ -114,6 +120,7 @@ public:
static const std::string mag_format_name;
private:
std::string m_format;
double m_scale_factor;
double m_dbu;
bool m_dont_write_empty_cells;

View File

@ -945,7 +945,7 @@ std::string
Cell::get_display_name () const
{
tl_assert (layout () != 0);
if (is_ghost_cell () && empty ()) {
if (is_real_ghost_cell ()) {
return std::string ("(") + layout ()->cell_name (cell_index ()) + std::string (")");
} else {
return layout ()->cell_name (cell_index ());
@ -959,6 +959,20 @@ Cell::set_name (const std::string &name)
layout ()->rename_cell (cell_index (), name.c_str ());
}
void
Cell::set_ghost_cell (bool g)
{
// NOTE: this change is not undo managed
if (m_ghost_cell != g) {
m_ghost_cell = g;
tl_assert (layout () != 0);
// To trigger a redraw and cell tree rebuild
layout ()->cell_name_changed ();
}
}
void
Cell::check_locked () const
{

View File

@ -925,15 +925,26 @@ public:
return m_ghost_cell;
}
/**
* @brief Gets a value indicating whether the cell is a "real" ghost cell
*
* A ghost cell is a real ghost cell only if the ghost cell flag is set
* and the cell is empty. Only in that case for example the cell is written
* to GDS files as a ghost cell.
*
* Otherwise, the ghost cell flag is mostly ignored.
*/
bool is_real_ghost_cell () const
{
return m_ghost_cell && empty ();
}
/**
* @brief Sets the "ghost cell" flag
*
* See "is_ghost_cell" for a description of this property.
*/
void set_ghost_cell (bool g)
{
m_ghost_cell = g;
}
void set_ghost_cell (bool g);
/**
* @brief Gets a value indicating whether the cell is locked

View File

@ -859,7 +859,7 @@ namespace
struct generic_result_adaptor
{
public:
generic_result_adaptor<T> (std::vector<std::unordered_set<T> > *results)
generic_result_adaptor (std::vector<std::unordered_set<T> > *results)
: mp_results (results)
{
m_intermediate.reserve (results->size ());

View File

@ -1758,7 +1758,7 @@ public:
* Creates a local operation which utilizes the operation tree. "node" is the root of the operation tree.
* Ownership of the node is *not* transferred to the local operation.
*/
compound_local_operation<TS, TI, TR> (CompoundRegionOperationNode *node)
compound_local_operation (CompoundRegionOperationNode *node)
: mp_node (node)
{ }
@ -1802,7 +1802,7 @@ public:
* Creates a local operation which utilizes the operation tree. "node" is the root of the operation tree.
* Ownership of the node is *not* transferred to the local operation.
*/
compound_local_operation_with_properties<TS, TI, TR> (CompoundRegionOperationNode *node, db::PropertyConstraint prop_constraint)
compound_local_operation_with_properties (CompoundRegionOperationNode *node, db::PropertyConstraint prop_constraint)
: mp_node (node), m_prop_constraint (prop_constraint)
{
// .. nothing yet ..

View File

@ -1686,6 +1686,69 @@ connected_clusters<T>::join_cluster_with (typename local_cluster<T>::id_type id,
}
}
template <class T>
void
connected_clusters<T>::join_clusters_with (typename local_cluster<T>::id_type id, typename std::set<typename local_cluster<T>::id_type>::const_iterator with_from, typename std::set<typename local_cluster<T>::id_type>::const_iterator with_to)
{
if (with_from != with_to && *with_from == id) {
++with_from;
}
if (with_from == with_to) {
return;
}
connections_type &target = m_connections [id];
std::set<connections_type::value_type> target_set;
bool target_set_valid = false;
for (auto w = with_from; w != with_to; ++w) {
local_clusters<T>::join_cluster_with (id, *w);
// handle the connections by translating
typename std::map<typename local_cluster<T>::id_type, connections_type>::iterator tc = m_connections.find (*w);
if (tc != m_connections.end ()) {
connections_type &to_join = tc->second;
for (connections_type::const_iterator c = to_join.begin (); c != to_join.end (); ++c) {
m_rev_connections [*c] = id;
}
if (target.empty ()) {
target.swap (to_join);
} else if (! to_join.empty ()) {
// Join while removing duplicates
if (! target_set_valid) {
target_set.insert (target.begin (), target.end ());
target_set_valid = true;
}
for (auto j = to_join.begin (); j != to_join.end (); ++j) {
if (target_set.find (*j) == target_set.end ()) {
target.push_back (*j);
target_set.insert (*j);
}
}
}
m_connections.erase (tc);
}
if (m_connected_clusters.find (*w) != m_connected_clusters.end ()) {
m_connected_clusters.insert (id);
m_connected_clusters.erase (*w);
}
}
}
template <class T>
typename local_cluster<T>::id_type
connected_clusters<T>::find_cluster_with_connection (const ClusterInstance &inst) const
@ -1995,8 +2058,9 @@ public:
typename std::set<id_type>::const_iterator c = sc->begin ();
typename std::set<id_type>::const_iterator cc = c;
for (++cc; cc != sc->end (); ++cc) {
mp_cell_clusters->join_cluster_with (*c, *cc);
++cc;
if (cc != sc->end ()) {
mp_cell_clusters->join_clusters_with (*c, cc, sc->end ());
}
}

View File

@ -1274,6 +1274,13 @@ public:
*/
void join_cluster_with (typename local_cluster<T>::id_type id, typename local_cluster<T>::id_type with_id);
/**
* @brief Joins a cluster id with the a set of clusters given by an iterator interval with_to .. with_from
*
* This function is equivalent to calling "join_cluster_with" multiple times, but more efficient.
*/
void join_clusters_with (typename local_cluster<T>::id_type id, typename std::set<typename local_cluster<T>::id_type>::const_iterator with_from, typename std::set<typename local_cluster<T>::id_type>::const_iterator with_to);
/**
* @brief An iterator delivering all clusters (even the connectors)
*

View File

@ -714,7 +714,6 @@ local_processor_result_computation_task<TS, TI, TR>::perform ()
{
mp_cell_contexts->compute_results (*mp_contexts, mp_cell, mp_op, m_output_layers, mp_proc);
// erase the contexts we don't need any longer
{
tl::MutexLocker locker (& mp_contexts->lock ());
@ -734,7 +733,10 @@ local_processor_result_computation_task<TS, TI, TR>::perform ()
}
#endif
mp_contexts->context_map ().erase (mp_cell);
// release some memory
auto ctx = mp_contexts->context_map ().find (mp_cell);
tl_assert (ctx != mp_contexts->context_map ().end ());
ctx->second.cleanup ();
}
}
@ -881,15 +883,6 @@ void local_processor<TS, TI, TR>::run (local_operation<TS, TI, TR> *op, unsigned
compute_results (contexts, op, output_layers);
}
template <class TS, class TI, class TR>
void local_processor<TS, TI, TR>::push_results (db::Cell *cell, unsigned int output_layer, const std::unordered_set<TR> &result) const
{
if (! result.empty ()) {
tl::MutexLocker locker (&cell->layout ()->lock ());
cell->shapes (output_layer).insert (result.begin (), result.end ());
}
}
template <class TS, class TI, class TR>
void local_processor<TS, TI, TR>::compute_contexts (local_processor_contexts<TS, TI, TR> &contexts, const local_operation<TS, TI, TR> *op, unsigned int subject_layer, const std::vector<unsigned int> &intruder_layers) const
{
@ -1248,7 +1241,7 @@ local_processor<TS, TI, TR>::compute_results (local_processor_contexts<TS, TI, T
typename local_processor_contexts<TS, TI, TR>::iterator cpc = contexts.context_map ().find (&mp_subject_layout->cell (*bu));
if (cpc != contexts.context_map ().end ()) {
cpc->second.compute_results (contexts, cpc->first, op, output_layers, this);
contexts.context_map ().erase (cpc);
cpc->second.cleanup (); // release some memory
}
}
@ -1261,6 +1254,24 @@ local_processor<TS, TI, TR>::compute_results (local_processor_contexts<TS, TI, T
}
}
// deliver the results
{
tl::MutexLocker locker (& mp_subject_layout->lock ());
for (auto c = contexts.begin (); c != contexts.end (); ++c) {
db::Cell *cell = c->first;
auto r = c->second.result ().begin ();
auto rend = c->second.result ().end ();
for (auto o = output_layers.begin (); r != rend && o != output_layers.end (); ++o, ++r) {
if (! r->empty ()) {
cell->shapes (*o).insert (r->begin (), r->end ());
}
}
}
}
}
namespace {

View File

@ -244,9 +244,20 @@ public:
return m_contexts.end ();
}
const std::vector<std::unordered_set<TR> > &result () const
{
return m_result;
}
void cleanup ()
{
m_contexts.clear ();
}
private:
const db::Cell *mp_intruder_cell;
std::unordered_map<context_key_type, db::local_processor_cell_context<TS, TI, TR> > m_contexts;
std::vector<std::unordered_set<TR> > m_result;
};
template <class TS, class TI, class TR>
@ -558,7 +569,6 @@ private:
size_t get_progress () const;
void compute_contexts (db::local_processor_contexts<TS, TI, TR> &contexts, db::local_processor_cell_context<TS, TI, TR> *parent_context, db::Cell *subject_parent, db::Cell *subject_cell, const db::ICplxTrans &subject_cell_inst, const db::Cell *intruder_cell, const typename local_processor_cell_contexts<TS, TI, TR>::context_key_type &intruders, db::Coord dist) const;
void issue_compute_contexts (db::local_processor_contexts<TS, TI, TR> &contexts, db::local_processor_cell_context<TS, TI, TR> *parent_context, db::Cell *subject_parent, db::Cell *subject_cell, const db::ICplxTrans &subject_cell_inst, const db::Cell *intruder_cell, typename local_processor_cell_contexts<TS, TI, TR>::context_key_type &intruders, db::Coord dist) const;
void push_results (db::Cell *cell, unsigned int output_layer, const std::unordered_set<TR> &result) const;
void compute_local_cell (const db::local_processor_contexts<TS, TI, TR> &contexts, db::Cell *subject_cell, const db::Cell *intruder_cell, const local_operation<TS, TI, TR> *op, const typename local_processor_cell_contexts<TS, TI, TR>::context_key_type &intruders, std::vector<std::unordered_set<TR> > &result) const;
bool subject_cell_is_breakout (db::cell_index_type ci) const

View File

@ -446,7 +446,12 @@ local_processor_cell_contexts<TS, TI, TR>::compute_results (const local_processo
proc->compute_local_cell (contexts, cell, mp_intruder_cell, op, *c->first, res);
}
if (common.empty ()) {
bool common_empty = true;
for (auto c = common.begin (); common_empty && c != common.end (); ++c) {
common_empty = c->empty ();
}
if (common_empty) {
CRONOLOGY_COMPUTE_BRACKET(event_propagate)
for (std::vector<unsigned int>::const_iterator o = output_layers.begin (); o != output_layers.end (); ++o) {
@ -519,9 +524,30 @@ local_processor_cell_contexts<TS, TI, TR>::compute_results (const local_processo
}
for (std::vector<unsigned int>::const_iterator o = output_layers.begin (); o != output_layers.end (); ++o) {
size_t oi = o - output_layers.begin ();
proc->push_results (cell, *o, common[oi]);
// store the results
bool common_empty = true;
for (auto c = common.begin (); common_empty && c != common.end (); ++c) {
common_empty = c->empty ();
}
if (! common_empty) {
if (m_result.empty ()) {
m_result.swap (common);
} else {
if (m_result.size () < common.size ()) {
m_result.resize (common.size ());
}
auto t = m_result.begin ();
for (auto s = common.begin (); s != common.end (); ++s, ++t) {
if (t->empty ()) {
t->swap (*s);
} else {
t->insert (s->begin (), s->end ());
s->clear ();
}
}
}
}
}

View File

@ -1417,7 +1417,7 @@ Layout::add_cell (const char *name)
if (cm != m_cell_map.end ()) {
const db::Cell &c= cell (cm->second);
if (c.is_ghost_cell () && c.empty ()) {
if (c.is_real_ghost_cell ()) {
// ghost cells are available as new cells - the idea is to
// treat them as non-existing.
return cm->second;

View File

@ -183,6 +183,14 @@ public:
return m_busy;
}
/**
* @brief Issue a "cell name changed event"
*/
void cell_name_changed ()
{
cell_name_changed_event ();
}
protected:
friend class PropertiesRepository;
@ -191,14 +199,6 @@ protected:
*/
virtual void do_update () { }
/**
* @brief Issue a "prop id's changed event"
*/
void cell_name_changed ()
{
cell_name_changed_event ();
}
/**
* @brief Issue a "layer properties changed event"
*/

View File

@ -69,19 +69,19 @@ class polygon_properties_filter
: public PolygonFilter, public PropertiesFilter
{
public:
polygon_properties_filter<PolygonFilter> (const tl::Variant &name, const tl::GlobPattern &pattern, bool inverse)
polygon_properties_filter (const tl::Variant &name, const tl::GlobPattern &pattern, bool inverse)
: PropertiesFilter (name, pattern, inverse)
{
// .. nothing yet ..
}
polygon_properties_filter<PolygonFilter> (const tl::Variant &name, const tl::Variant &value, bool inverse)
polygon_properties_filter(const tl::Variant &name, const tl::Variant &value, bool inverse)
: PropertiesFilter (name, value, inverse)
{
// .. nothing yet ..
}
polygon_properties_filter<PolygonFilter> (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse)
polygon_properties_filter (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse)
: PropertiesFilter (name, from, to, inverse)
{
// .. nothing yet ..
@ -103,19 +103,19 @@ class generic_properties_filter
: public BasicFilter, public PropertiesFilter
{
public:
generic_properties_filter<BasicFilter, ShapeType> (const tl::Variant &name, const tl::GlobPattern &pattern, bool inverse)
generic_properties_filter (const tl::Variant &name, const tl::GlobPattern &pattern, bool inverse)
: PropertiesFilter (name, pattern, inverse)
{
// .. nothing yet ..
}
generic_properties_filter<BasicFilter, ShapeType> (const tl::Variant &name, const tl::Variant &value, bool inverse)
generic_properties_filter (const tl::Variant &name, const tl::Variant &value, bool inverse)
: PropertiesFilter (name, value, inverse)
{
// .. nothing yet ..
}
generic_properties_filter<BasicFilter, ShapeType> (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse)
generic_properties_filter (const tl::Variant &name, const tl::Variant &from, const tl::Variant &to, bool inverse)
: PropertiesFilter (name, from, to, inverse)
{
// .. nothing yet ..

View File

@ -27,6 +27,7 @@
#include "tlString.h"
#include "tlAssert.h"
#include "tlHash.h"
#include "tlStaticObjects.h"
namespace db
{
@ -348,13 +349,21 @@ PropertiesSet::hash () const
// ----------------------------------------------------------------------------------
// PropertiesRepository implementation
static PropertiesRepository s_instance;
static PropertiesRepository *sp_global_instance = 0;
static PropertiesRepository *sp_temp_instance = 0;
PropertiesRepository &
PropertiesRepository::instance ()
{
return sp_temp_instance ? *sp_temp_instance : s_instance;
if (sp_temp_instance) {
return *sp_temp_instance;
} else {
if (! sp_global_instance) {
sp_global_instance = new PropertiesRepository ();
tl::StaticObjects::reg (&sp_global_instance);
}
return *sp_global_instance;
}
}
void

View File

@ -89,7 +89,7 @@ struct simple_result_inserter
{
typedef TR value_type;
simple_result_inserter<TR> (std::unordered_set<TR> &result)
simple_result_inserter (std::unordered_set<TR> &result)
: mp_result (&result)
{
// .. nothing yet ..
@ -232,6 +232,8 @@ check_local_operation_base<TS, TI>::compute_results (db::Layout *layout, db::Cel
bool take_all = edge_check.has_negative_edge_output () || intruders.empty ();
db::Box common_box;
bool subjects_are_fully_covered = false;
if (! take_all) {
db::Vector e (edge_check.distance (), edge_check.distance ());
@ -241,14 +243,24 @@ check_local_operation_base<TS, TI>::compute_results (db::Layout *layout, db::Cel
subject_box += db::box_convert<TS> () (**i);
}
if (edge_check.requires_different_layers ()) {
db::Box intruder_box;
common_box = subject_box.enlarged (e);
if (edge_check.requires_different_layers () && ! common_box.empty ()) {
subjects_are_fully_covered = false;
db::Box all_intruders_box;
for (auto i = intruders.begin (); i != intruders.end (); ++i) {
intruder_box += db::box_convert<TI> () (**i);
db::Box intruder_box = db::box_convert<TI> () (**i);
if (! subjects_are_fully_covered && (*i)->is_box () && common_box.inside (intruder_box)) {
subjects_are_fully_covered = true;
}
all_intruders_box += intruder_box.enlarged (e);
}
common_box = subject_box.enlarged (e) & intruder_box.enlarged (e);
} else {
common_box = subject_box.enlarged (e);
common_box &= all_intruders_box;
}
}
@ -306,6 +318,22 @@ check_local_operation_base<TS, TI>::compute_results (db::Layout *layout, db::Cel
// empty intruders
} else if (subjects_are_fully_covered) {
// optimization: can use a single box for the intruders
n = 1;
db::Point ul = common_box.upper_left ();
db::Point lr = common_box.lower_right ();
poly_check.enter (db::Edge (common_box.p1 (), ul), n);
poly_check.enter (db::Edge (ul, common_box.p2 ()), n);
poly_check.enter (db::Edge (common_box.p2 (), lr), n);
poly_check.enter (db::Edge (lr, common_box.p1 ()), n);
n += 2;
} else if (! m_other_is_merged && (intruders.size () > 1 || ! (*intruders.begin ())->is_box ())) {
// NOTE: this local merge is not necessarily giving the same results than a global merge before running

View File

@ -3450,6 +3450,9 @@ Class<db::Cell> decl_Cell ("db", "Cell",
"To satisfy the references inside the layout, a dummy cell is created in this case\n"
"which has the \"ghost cell\" flag set to true.\n"
"\n"
"A ghost cell is a real ghost cell only if the cell is empty. In that case, it is written "
"as a ghost cell to GDS files for example. If a cell is not empty, this flag is ignored.\n"
"\n"
"This method has been introduced in version 0.20.\n"
) +
gsi::method ("ghost_cell=", &db::Cell::set_ghost_cell, gsi::arg ("flag"),

View File

@ -503,3 +503,19 @@ TEST(SameValueDifferentTypes)
EXPECT_EQ (db::property_name (rp.prop_name_id ((int) 5)).to_parsable_string (), "#5");
}
}
TEST(ComplexTypes)
{
// This is also a smoke test: we intentionally register globally as the finalization code
// is critical: without the right destruction order we destroy the class object before the
// variant and trigger an assertion (pure virtual function called)
db::PropertiesSet ps;
ps.insert (tl::Variant (17), db::DBox (0, 0, 1.5, 2.5));
db::properties_id_type pid = db::properties_id (ps);
auto const &ps_out = db::properties (pid);
EXPECT_EQ (ps_out.to_dict_var ().to_string (), "{17=>(0,0;1.5,2.5)}");
}

View File

@ -942,15 +942,18 @@ module DRC
# Turns profiling on or off (default). In profiling mode, the
# system will collect statistics about rules executed, their execution time
# and memory information. The argument specifies how many operations to
# print at the end of the run. Without an argument, all operations are
# print at the end of the run. Without an argument or when passing "true", all operations are
# printed. Passing "false" for the argument will disable profiling. This is the
# default.
def profile(n = 0)
if !n.is_a?(1.class) && n != nil && n != false
raise("Argument to 'profile' must be either an integer number or nil")
if !n.is_a?(1.class) && n != nil && n != false && n != true
raise("Argument to 'profile' must be either an integer number, true, false or nil")
end
@profile = !!n
if n == true
n = 0
end
@profile_n = [n || 0, 0].max
end

View File

@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
<string>Round Corners</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0" colspan="3">

View File

@ -1770,6 +1770,7 @@ PartialService::edit_cancel ()
ui ()->ungrab_mouse (this);
clear_mouse_cursors ();
close_editor_hooks (false);
selection_to_view ();

View File

@ -40,7 +40,7 @@ template <class X, class Y> struct address_of;
template<class X>
struct address_of<X, X>
{
address_of<X, X> () : b () { }
address_of () : b () { }
const void *operator() (const X &x) const { b = x; return &b; }
mutable X b;
};

View File

@ -204,12 +204,16 @@ msvc {
# -Wno-reserved-user-defined-literal \
#
lessThan(QT_MAJOR_VERSION, 6) {
# because we use unordered_map/unordered_set:
QMAKE_CXXFLAGS += -std=c++11
equals(HAVE_CPP20, "1") {
QMAKE_CXXFLAGS += -std=c++20
DEFINES += HAVE_CPP20
} else {
# because we use unordered_map/unordered_set:
QMAKE_CXXFLAGS += -std=c++17
lessThan(QT_MAJOR_VERSION, 6) {
# because we use unordered_map/unordered_set:
QMAKE_CXXFLAGS += -std=c++11
} else {
QMAKE_CXXFLAGS += -std=c++17
}
}
win32 {

View File

@ -86,87 +86,6 @@
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QFrame" name="frame_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="_14">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="fill_area_cbx">
<property name="toolTip">
<string>Select how the region to fill is specified.</string>
</property>
<item>
<property name="text">
<string>All (whole cell)</string>
</property>
</item>
<item>
<property name="text">
<string>Shapes on layer ...</string>
</property>
</item>
<item>
<property name="text">
<string>Selected shapes</string>
</property>
</item>
<item>
<property name="text">
<string>Single box with ...</string>
</property>
</item>
<item>
<property name="text">
<string>Ruler bounding boxes</string>
</property>
</item>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QFrame" name="frame_6">
<property name="frameShape">
@ -277,22 +196,6 @@
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="0" colspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>241</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<spacer>
<property name="orientation">
@ -316,6 +219,22 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>241</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_6">
@ -436,6 +355,87 @@
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QFrame" name="frame_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="_14">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="fill_area_cbx">
<property name="toolTip">
<string>Select how the region to fill is specified.</string>
</property>
<item>
<property name="text">
<string>All (whole cell)</string>
</property>
</item>
<item>
<property name="text">
<string>Shapes on layer ...</string>
</property>
</item>
<item>
<property name="text">
<string>Selected shapes</string>
</property>
</item>
<item>
<property name="text">
<string>Single box with ...</string>
</property>
</item>
<item>
<property name="text">
<string>Ruler bounding boxes</string>
</property>
</item>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
@ -491,6 +491,9 @@
<property name="toolTip">
<string>The fill will not be generated over the areas specified by these layers</string>
</property>
<property name="currentIndex">
<number>3</number>
</property>
<item>
<property name="text">
<string>All layers</string>
@ -810,6 +813,83 @@
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_3">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Raster origin</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="origin_le">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>(x, y of lower-left corner of fill cell in µm)</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>135</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="second_order_fill_cb">
<property name="title">
@ -837,13 +917,6 @@
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="2">
<widget class="QLabel" name="label_60">
<property name="text">
<string>µm (keep distance between fill cells unless stitched)</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QLineEdit" name="fill_cell_2nd_le">
<property name="toolTip">
@ -851,16 +924,17 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="fill2_margin_le">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<item row="1" column="2">
<widget class="QLabel" name="label_60">
<property name="text">
<string>µm (keep distance between fill cells unless stitched)</string>
</property>
<property name="toolTip">
<string>Leave empty for no distance. Otherwise enter a distance in micron (can be anisotropic in the form &quot;dx,dy&quot;)</string>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_57">
<property name="text">
<string>Fill cell margin </string>
</property>
</widget>
</item>
@ -871,26 +945,6 @@
</property>
</widget>
</item>
<item row="1" column="3">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>141</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_57">
<property name="text">
<string>Fill cell margin </string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_59">
<property name="sizePolicy">
@ -904,16 +958,6 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="5">
<widget class="QLabel" name="label_58">
<property name="text">
<string>The second order fill cell is used to fill space remaining from the first fill step. Thus, the second order fill cell must be smaller than the first order fill cell. The boundary layer must be the same for the second order fill cell.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_56">
<property name="text">
@ -980,6 +1024,42 @@
</layout>
</widget>
</item>
<item row="1" column="3">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>141</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="3" column="0" colspan="5">
<widget class="QLabel" name="label_58">
<property name="text">
<string>The second order fill cell is used to fill space remaining from the first fill step. Thus, the second order fill cell must be smaller than the first order fill cell. The boundary layer must be the same for the second order fill cell.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="fill2_margin_le">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Leave empty for no distance. Otherwise enter a distance in micron (can be anisotropic in the form &quot;dx,dy&quot;)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -1030,6 +1110,7 @@
<tabstop>row_le</tabstop>
<tabstop>column_le</tabstop>
<tabstop>enhanced_cb</tabstop>
<tabstop>origin_le</tabstop>
<tabstop>second_order_fill_cb</tabstop>
<tabstop>fill_cell_2nd_le</tabstop>
<tabstop>choose_fc_2nd_pb</tabstop>

View File

@ -92,6 +92,7 @@ FillDialog::FillDialog (QWidget *parent, LayoutViewBase *view)
fill_area_stack->setCurrentIndex (0);
connect (fill_area_cbx, SIGNAL (currentIndexChanged (int)), this, SLOT (fill_area_changed (int)));
connect (enhanced_cb, SIGNAL (stateChanged (int)), this, SLOT (enhanced_fill_changed (int)));
connect (button_box, SIGNAL (accepted ()), this, SLOT (ok_pressed ()));
connect (choose_fc_pb, SIGNAL (clicked ()), this, SLOT (choose_fc ()));
connect (choose_fc_2nd_pb, SIGNAL (clicked ()), this, SLOT (choose_fc_2nd ()));
@ -167,6 +168,8 @@ FillDialog::generate_fill (const FillParameters &fp)
bool enhanced_fill = enhanced_cb->isChecked ();
db::Point fill_origin = db::CplxTrans (ly.dbu ()).inverted () * fp.fill_origin;
db::Coord exclude_x = db::coord_traits<db::Coord>::rounded (fp.exclude_distance.x () / ly.dbu ());
db::Coord exclude_y = db::coord_traits<db::Coord>::rounded (fp.exclude_distance.y () / ly.dbu ());
@ -232,8 +235,6 @@ FillDialog::generate_fill (const FillParameters &fp)
fill_region.merge ();
}
db::Box fr_bbox = fill_region.bbox ();
if (tl::verbosity () >= 20) {
tl::info << "Collecting exclude areas";
}
@ -263,8 +264,6 @@ FillDialog::generate_fill (const FillParameters &fp)
// Perform the NOT operation to create the fill region
fill_region -= es;
db::Region new_fill_area;
int step = 0;
do {
@ -276,7 +275,7 @@ FillDialog::generate_fill (const FillParameters &fp)
}
if (! enhanced_fill) {
db::fill_region (cv.cell (), fill_region, fill_cell->cell_index (), fc_bbox, row_step, column_step, fr_bbox.p1 (), false, fill_cell2 ? &fill_region : 0, fill_margin, fill_cell2 ? &fill_region : 0);
db::fill_region (cv.cell (), fill_region, fill_cell->cell_index (), fc_bbox, row_step, column_step, fill_origin, false, fill_cell2 ? &fill_region : 0, fill_margin, fill_cell2 ? &fill_region : 0);
} else {
db::fill_region_repeat (cv.cell (), fill_region, fill_cell->cell_index (), fc_bbox, row_step, column_step, fill_margin, fill_cell2 ? &fill_region : 0);
}
@ -478,6 +477,20 @@ FillDialog::get_fill_parameters ()
fp.enhanced_fill = enhanced_cb->isChecked ();
// read origin
x = 0.0, y = 0.0;
s = tl::to_string (origin_le->text ());
ex = tl::Extractor (s.c_str ());
if (ex.try_read (x)) {
if (ex.test (",") && ex.try_read (y)) {
// take x, y
} else {
y = x;
}
}
fp.fill_origin = db::DPoint (x, y);
db::DBox fc_bbox = db::CplxTrans (cv->layout ().dbu ()) * (fc_bbox_layer < 0 ? fill_cell->bbox () : fill_cell->bbox (fc_bbox_layer));
if (fc_bbox.empty ()) {
if (fc_bbox_layer >= 0) {
@ -492,7 +505,7 @@ FillDialog::get_fill_parameters ()
if (ex.try_read (x) && ex.test (",") && ex.try_read (y)) {
fp.row_step = db::DVector (x, y);
} else {
fp.row_step = db::DVector (fc_bbox.width (), 0.0);
fp.row_step = db::DVector (fc_bbox.width () + fp.fill_cell_margin.x (), 0.0);
}
s = tl::to_string (column_le->text ());
@ -500,7 +513,7 @@ FillDialog::get_fill_parameters ()
if (ex.try_read (x) && ex.test (",") && ex.try_read (y)) {
fp.column_step = db::DVector (x, y);
} else {
fp.column_step = db::DVector (0.0, fc_bbox.height ());
fp.column_step = db::DVector (0.0, fc_bbox.height () + fp.fill_cell_margin.y ());
}
fp.fc_bbox = fc_bbox;
@ -528,7 +541,7 @@ FillDialog::get_fill_parameters ()
if (ex.try_read (x) && ex.test (",") && ex.try_read (y)) {
fp.row_step2 = db::DVector (x, y);
} else {
fp.row_step2 = db::DVector (fc_bbox2.width (), 0.0);
fp.row_step2 = db::DVector (fc_bbox2.width () + fp.fill_cell_margin2.x (), 0.0);
}
s = tl::to_string (column_2nd_le->text ());
@ -536,7 +549,7 @@ FillDialog::get_fill_parameters ()
if (ex.try_read (x) && ex.test (",") && ex.try_read (y)) {
fp.column_step2 = db::DVector (x, y);
} else {
fp.column_step2 = db::DVector (0.0, fc_bbox2.height ());
fp.column_step2 = db::DVector (0.0, fc_bbox2.height () + fp.fill_cell_margin2.y ());
}
fp.fc_bbox2 = fc_bbox2;
@ -575,6 +588,12 @@ BEGIN_PROTECTED
END_PROTECTED
}
void
FillDialog::enhanced_fill_changed (int ef)
{
origin_le->setEnabled (ef != Qt::Checked);
}
void
FillDialog::fill_area_changed (int fa)
{

View File

@ -53,6 +53,7 @@ struct LAY_PUBLIC FillParameters
bool exclude_all_layers;
std::vector<db::LayerProperties> exclude_layers;
FillRegionMode fill_region_mode;
db::DPoint fill_origin;
db::Region fill_region;
db::LayerProperties fill_region_layer;
db::DVector exclude_distance;
@ -81,6 +82,7 @@ public:
public slots:
void fill_area_changed (int);
void enhanced_fill_changed (int);
void ok_pressed ();
void choose_fc ();
void choose_fc_2nd ();

View File

@ -220,7 +220,7 @@ Finder::do_find (const db::Cell &cell, int level, const db::DCplxTrans &vp, cons
&& (t * m_cell_box_convert (cell)).touches (m_scan_region)
&& (mp_view->select_inside_pcells_mode () || !cell.is_proxy ())
&& !mp_view->is_cell_hidden (cell.cell_index (), m_cv_index)
&& !cell.is_ghost_cell ()) {
&& !cell.is_real_ghost_cell ()) {
db::ICplxTrans it = t.inverted ();
db::Box scan_box (it * m_scan_region);
@ -846,7 +846,7 @@ InstFinder::reset_counter ()
bool
InstFinder::consider_cell (const db::Cell &cell) const
{
return cell.is_ghost_cell () ? m_consider_ghost_cells : m_consider_normal_cells;
return cell.is_real_ghost_cell () ? m_consider_ghost_cells : m_consider_normal_cells;
}
void
@ -877,7 +877,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
// just consider the instances exactly at the last level of
// hierarchy (this is where the boxes are drawn) or of cells that
// are hidden.
if (level == max_level () - 1 || inst_cell.is_proxy () || inst_cell.is_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
if (level == max_level () - 1 || inst_cell.is_proxy () || inst_cell.is_real_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
db::box_convert <db::CellInst, false> bc (layout ());
for (db::CellInstArray::iterator p = cell_inst.begin_touching (search_box, bc); ! p.at_end (); ++p) {
@ -885,7 +885,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
checkpoint ();
db::Box ibox;
if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || inst_cell.is_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || inst_cell.is_real_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
ibox = inst_cell.bbox_with_empty ();
} else if (inst_cell.bbox ().empty ()) {
// empty cells cannot be found by visible layers, so we always select them here
@ -954,7 +954,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
// just consider the instances exactly at the last level of
// hierarchy (this is where the boxes are drawn) or if of cells that
// are hidden.
if (level == max_level () - 1 || inst_cell.is_proxy () || inst_cell.is_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
if (level == max_level () - 1 || inst_cell.is_proxy () || inst_cell.is_real_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
db::box_convert <db::CellInst, false> bc (layout ());
for (db::CellInstArray::iterator p = cell_inst.begin_touching (search_box, bc); ! p.at_end (); ++p) {
@ -965,7 +965,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d
double d = std::numeric_limits<double>::max ();
db::Box ibox;
if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || inst_cell.is_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
if (! m_visible_layers || level == mp_view->get_max_hier_levels () - 1 || inst_cell.is_real_ghost_cell () || mp_view->is_cell_hidden (inst_cell.cell_index (), m_cv_index)) {
ibox = inst_cell.bbox_with_empty ();
} else if (inst_cell.bbox ().empty ()) {
// empty cells cannot be found by visible layers, so we always select them here

View File

@ -269,9 +269,12 @@ MoveService::mouse_double_click_event (const db::DPoint &p, unsigned int buttons
handle_click (p, buttons, false, 0);
}
lay::SelectionService *selector = mp_view->selection_service ();
if (selector) {
return selector->mouse_double_click_event (p, buttons, prio);
if (is_active ()) {
// in move mode, a double click opens the properties dialog
lay::SelectionService *selector = mp_view->selection_service ();
if (selector) {
return selector->mouse_double_click_event (p, buttons, prio);
}
}
}

View File

@ -714,7 +714,7 @@ RedrawThreadWorker::setup (LayoutViewBase *view, RedrawThreadCanvas *canvas, con
std::set <lay::LayoutViewBase::cell_index_type> &gc = m_ghost_cells [i];
const db::Layout &ly = view->cellview (i)->layout ();
for (auto c = ly.begin (); c != ly.end (); ++c) {
if (c->is_ghost_cell ()) {
if (c->is_real_ghost_cell ()) {
gc.insert (c->cell_index ());
}
}
@ -950,12 +950,12 @@ RedrawThreadWorker::draw_boxes_impl (bool drawing_context, db::cell_index_type c
bbox_for_label = bbox;
}
if (for_ghosts && cell.is_ghost_cell ()) {
if (for_ghosts && cell.is_real_ghost_cell ()) {
// paint the box on this level
draw_cell (drawing_context, level, trans, bbox, bbox_for_label, empty_cell, mp_layout->display_name (ci), opt_bitmap);
} else if (! for_ghosts && ! cell.is_ghost_cell () && (level == m_to_level || (m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()))) {
} else if (! for_ghosts && ! cell.is_real_ghost_cell () && (level == m_to_level || (m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()))) {
// paint the box on this level
draw_cell (drawing_context, level, trans, bbox, bbox_for_label, empty_cell, mp_layout->display_name (ci), opt_bitmap);
@ -1186,12 +1186,12 @@ RedrawThreadWorker::draw_box_properties_impl (bool drawing_context, db::cell_ind
// small cell dropped
} else if (for_ghosts && cell.is_ghost_cell ()) {
} else if (for_ghosts && cell.is_real_ghost_cell ()) {
// paint the box on this level
draw_cell_properties (drawing_context, level, trans, bbox, prop_id);
} else if (! for_ghosts && ! cell.is_ghost_cell () && (level == m_to_level || (m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()))) {
} else if (! for_ghosts && ! cell.is_real_ghost_cell () && (level == m_to_level || (m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()))) {
// paint the box on this level
draw_cell_properties (drawing_context, level, trans, bbox, prop_id);
@ -1317,7 +1317,7 @@ RedrawThreadWorker::any_shapes (db::cell_index_type cell_index, unsigned int lev
// Ghost cells are not drawn either
const db::Cell &cell = mp_layout->cell (cell_index);
if (cell.is_ghost_cell ()) {
if (cell.is_real_ghost_cell ()) {
return false;
}
@ -1359,7 +1359,7 @@ RedrawThreadWorker::any_cell_box (db::cell_index_type cell_index, unsigned int l
// ghost cells are also drawn
const db::Cell &cell = mp_layout->cell (cell_index);
if (cell.is_ghost_cell ()) {
if (cell.is_real_ghost_cell ()) {
return true;
}
@ -1400,7 +1400,7 @@ RedrawThreadWorker::any_text_shapes (db::cell_index_type cell_index, unsigned in
// Ghost cells are not drawn either
const db::Cell &cell = mp_layout->cell (cell_index);
if (cell.is_ghost_cell ()) {
if (cell.is_real_ghost_cell ()) {
return false;
}
@ -1503,7 +1503,7 @@ RedrawThreadWorker::draw_text_layer (bool drawing_context, db::cell_index_type c
} else if (! bbox.empty ()) {
bool hidden = cell.is_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()));
bool hidden = cell.is_real_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (ci) != m_hidden_cells [m_cv_index].end ()));
bool need_to_dive = (level + 1 < m_to_level) && ! hidden;
db::Box cell_bbox = cell.bbox ();
@ -1637,7 +1637,7 @@ RedrawThreadWorker::draw_text_layer (bool drawing_context, db::cell_index_type c
++inst;
db::cell_index_type new_ci = cell_inst.object ().cell_index ();
bool hidden = mp_layout->cell (new_ci).is_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (new_ci) != m_hidden_cells [m_cv_index].end ()));
bool hidden = mp_layout->cell (new_ci).is_real_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (new_ci) != m_hidden_cells [m_cv_index].end ()));
db::Box cell_box = mp_layout->cell (new_ci).bbox (m_layer);
if (! cell_box.empty () && ! hidden) {
@ -1939,7 +1939,7 @@ RedrawThreadWorker::draw_layer_wo_cache (int from_level, int to_level, db::cell_
++inst;
db::cell_index_type new_ci = cell_inst.object ().cell_index ();
bool hidden = mp_layout->cell (new_ci).is_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (new_ci) != m_hidden_cells [m_cv_index].end ()));
bool hidden = mp_layout->cell (new_ci).is_real_ghost_cell () || ((m_cv_index < int (m_hidden_cells.size ()) && m_hidden_cells [m_cv_index].find (new_ci) != m_hidden_cells [m_cv_index].end ()));
db::Box new_cell_box = mp_layout->cell (new_ci).bbox (m_layer);
if (! new_cell_box.empty () && ! hidden) {
@ -2096,7 +2096,7 @@ RedrawThreadWorker::draw_layer (int from_level, int to_level, db::cell_index_typ
db::Box cell_bbox = cell.bbox ();
// Nothing to draw
if (bbox.empty () || cell.is_ghost_cell ()) {
if (bbox.empty () || cell.is_real_ghost_cell ()) {
return;
}

View File

@ -34,7 +34,7 @@ class shape_as_user_object
: public db::DUserObjectBase
{
public:
shape_as_user_object<Sh> (const Sh &sh)
shape_as_user_object (const Sh &sh)
: m_shape (sh)
{ }

View File

@ -156,9 +156,13 @@ Class<lay::LayoutView> decl_LayoutView (decl_LayoutViewBase, "lay", "LayoutView"
gsi::constructor ("new", &new_view, gsi::arg ("editable", false), gsi::arg ("manager", (db::Manager *) 0, "nil"), gsi::arg ("options", (unsigned int) 0),
"@brief Creates a standalone view\n"
"\n"
"This constructor is for special purposes only. To create a view in the context of a main window, "
"This constructor creates a non-GUI, standalone layout view. It is not attached to a Qt object and can be used for generating "
"images through the screenshot method for example. To create a view in the context of a main window, "
"use \\MainWindow#create_view and related methods.\n"
"\n"
"A standalone layout view has a default configuration that is different from a standard view - for example the "
"color scheme is not initialized according to screen settings.\n"
"\n"
"@param editable True to make the view editable\n"
"@param manager The \\Manager object to enable undo/redo\n"
"@param options A combination of the values in the LV_... constants from \\LayoutViewBase\n"

View File

@ -577,7 +577,7 @@ GDS2WriterBase::write (db::Layout &layout, tl::OutputStream &stream, const db::S
// don't write ghost cells unless they are not empty (any more)
// also don't write proxy cells which are not employed
if ((! cref.is_ghost_cell () || ! cref.empty ()) && (! cref.is_proxy () || ! cref.is_top ())) {
if (! cref.is_real_ghost_cell () && (! cref.is_proxy () || ! cref.is_top ())) {
try {
write_cell (layout, cref, layers, cell_set, sf, time_data);

View File

@ -913,7 +913,7 @@ Writer::make_meta_data (const db::Cell *cell, stream::metaData::MetaData::Builde
void
Writer::write_cell (db::cell_index_type ci, kj::BufferedOutputStream &os)
{
bool needs_layout_view = ! mp_layout->cell (ci).is_ghost_cell ();
bool needs_layout_view = ! mp_layout->cell (ci).is_real_ghost_cell ();
bool needs_meta_data_view = mp_layout->begin_meta (ci) != mp_layout->end_meta (ci);
capnp::MallocMessageBuilder message;

View File

@ -1322,21 +1322,37 @@ OASISReader::resolve_forward_references (db::PropertiesSet &properties)
const tl::Variant &name = db::property_name (p->first);
if (name.is_id ()) {
std::map <uint64_t, db::property_names_id_type>::iterator pf = m_propname_forward_references.find (name.to_id ());
uint64_t name_id = name.to_id ();
bool is_sprop = (name_id & 1) != 0;
name_id >>= 1;
std::map <uint64_t, db::property_names_id_type>::iterator pf = m_propname_forward_references.find (name_id);
if (pf != m_propname_forward_references.end ()) {
if (pf->second == m_s_gds_property_name_id) {
if (pf->second == m_klayout_context_property_name_id) {
// NOTE: property names ID 0 is reserved for context strings
new_props.insert (property_names_id_type (0), value);
} else if (! m_read_properties) {
// ignore other properties
} else if (pf->second == m_s_gds_property_name_id && is_sprop) {
// S_GDS_PROPERTY translation
if (value.is_list () && value.get_list ().size () >= 2) {
new_props.insert (value.get_list () [0], value.get_list () [1]);
}
} else if (pf->second == m_klayout_context_property_name_id) {
// NOTE: property names ID 0 is reserved for context strings
new_props.insert (property_names_id_type (0), value);
} else if (is_sprop && ! m_read_all_properties) {
// ignore system properties
} else {
new_props.insert (pf->second, value);
}
}
@ -1395,10 +1411,27 @@ OASISReader::replace_forward_references_in_variant (tl::Variant &v)
}
}
static void
store_properties (db::PropertiesSet &properties, db::property_names_id_type name, const db::OASISReader::property_value_list &value)
{
if (value.size () == 0) {
properties.insert (name, tl::Variant ());
} else if (value.size () == 1) {
properties.insert (name, tl::Variant (value [0]));
} else if (value.size () > 1) {
properties.insert (name, tl::Variant (value.begin (), value.end ()));
}
}
void
OASISReader::store_last_properties (db::PropertiesSet &properties, bool ignore_special, bool with_context_props)
{
if (with_context_props && mm_last_property_name.get () == m_klayout_context_property_name_id) {
const tl::Variant &name = db::property_name (mm_last_property_name.get ());
if (name.is_id ()) {
store_properties (properties, mm_last_property_name.get (), mm_last_value_list.get ());
} else if (with_context_props && mm_last_property_name.get () == m_klayout_context_property_name_id) {
// Context properties are stored with a special property name ID of 0
@ -1422,12 +1455,10 @@ OASISReader::store_last_properties (db::PropertiesSet &properties, bool ignore_s
// This is mode is used for cells and layouts so the standard properties do not appear as user properties.
// For shapes we need to keep the special ones since they may be forward-references S_GDS_PROPERTY names.
} else if (mm_last_value_list.get ().size () == 0) {
properties.insert (mm_last_property_name.get (), tl::Variant ());
} else if (mm_last_value_list.get ().size () == 1) {
properties.insert (mm_last_property_name.get (), tl::Variant (mm_last_value_list.get () [0]));
} else if (mm_last_value_list.get ().size () > 1) {
properties.insert (mm_last_property_name.get (), tl::Variant (mm_last_value_list.get ().begin (), mm_last_value_list.get ().end ()));
} else {
store_properties (properties, mm_last_property_name.get (), mm_last_value_list.get ());
}
}
@ -1510,7 +1541,7 @@ OASISReader::read_properties ()
std::map <uint64_t, std::string>::const_iterator cid = m_propnames.find (id);
if (cid == m_propnames.end ()) {
mm_last_property_name = db::property_names_id (tl::Variant (id, true /*dummy for id type*/));
mm_last_property_name = db::property_names_id (tl::Variant ((id << 1) + uint64_t (is_sprop ? 1 : 0), true /*dummy for id type*/));
m_propname_forward_references.insert (std::make_pair (id, db::property_names_id_type (0)));
} else {
mm_last_property_name = db::property_names_id (tl::Variant (cid->second));

View File

@ -1443,12 +1443,6 @@ static bool must_write_cell (const db::Cell &cref)
return ! cref.is_proxy () || ! cref.is_top ();
}
static bool skip_cell_body (const db::Cell &cref)
{
// Skip cell bodies for ghost cells unless empty (they are not longer ghost cells in this case)
return cref.is_ghost_cell () && cref.empty ();
}
void
OASISWriter::create_cell_nstrings (const db::Layout &layout, const std::set <db::cell_index_type> &cell_set)
{
@ -1670,7 +1664,7 @@ OASISWriter::write (db::Layout &layout, tl::OutputStream &stream, const db::Save
mp_cell = &cref;
// skip cell body if the cell is not to be written
if (skip_cell_body (cref)) {
if (cref.is_real_ghost_cell ()) {
continue;
}

View File

@ -38,7 +38,7 @@ static void set_oasis_read_all_properties (db::LoadLayoutOptions *options, bool
options->get_options<db::OASISReaderOptions> ().read_all_properties = f;
}
static int get_oasis_read_all_properties (const db::LoadLayoutOptions *options)
static bool get_oasis_read_all_properties (const db::LoadLayoutOptions *options)
{
return options->get_options<db::OASISReaderOptions> ().read_all_properties;
}

View File

@ -1214,8 +1214,9 @@ property_setter_impl (int mid, PyObject *self, PyObject *value)
// check arguments (count and type)
bool is_valid = (*m)->compatible_with_num_args (1);
bool loose = (pass != 0); // loose in the second pass
if (is_valid && ! test_arg (*(*m)->begin_arguments (), value, loose, loose)) {
bool loose = (pass > 0); // loose in the second and third pass
bool object_substitution = (pass > 1); // object substitution in the third pass
if (is_valid && ! test_arg (*(*m)->begin_arguments (), value, loose, object_substitution)) {
is_valid = false;
}
@ -1228,7 +1229,7 @@ property_setter_impl (int mid, PyObject *self, PyObject *value)
++pass;
} while (! meth && pass < 2);
} while (! meth && pass < 3);
}

View File

@ -1676,6 +1676,15 @@ Class<rdb::Database> decl_ReportDatabase ("rdb", "ReportDatabase",
"\n"
"This method has been added in version 0.29.1."
) +
gsi::method ("merge", &rdb::Database::merge, gsi::arg ("other"),
"@brief Merges the other database with this one\n"
"This method will merge the other database with this one. The other database needs to have the same top cell than\n"
"this database. In the merge step, identical cells and categories will be identified and missing cells or categories\n"
"will be created in this database. After that, all items will be transferred from the other database into this one\n"
"and will be associated with cells and categories from this database.\n"
"\n"
"This method has been added in version 0.30.7."
) +
gsi::method ("is_modified?", &rdb::Database::is_modified,
"@brief Returns a value indicating whether the database has been modified\n"
) +

View File

@ -958,11 +958,18 @@ Tags::tag (id_type id)
void
Tags::import_tag (const Tag &t)
{
Tag &tt = tag (t.name (), t.is_user_tag ());
tt.set_description (t.description ());
import_tag_with_ref (t);
}
bool
Tag &
Tags::import_tag_with_ref (const Tag &t)
{
Tag &tt = tag (t.name (), t.is_user_tag ());
tt.set_description (t.description ());
return tt;
}
bool
Tags::has_tag (const std::string &name, bool user_tag) const
{
return m_ids_for_names.find (std::make_pair (name, user_tag)) != m_ids_for_names.end ();
@ -1314,6 +1321,13 @@ Database::import_tags (const Tags &tags)
}
}
Tag &
Database::import_tag (const Tag &tag)
{
set_modified ();
return m_tags.import_tag_with_ref (tag);
}
void
Database::import_categories (Categories *categories)
{
@ -1880,41 +1894,52 @@ namespace
};
}
static void map_category (const rdb::Category &cat, const rdb::Database &db, std::map<id_type, id_type> &cat2cat)
static void map_category (const rdb::Category &cat, rdb::Database &db, std::map<id_type, id_type> &cat2cat, std::map<id_type, id_type> &rev_cat2cat, bool create_missing, rdb::Category *parent)
{
const rdb::Category *this_cat = db.category_by_name (cat.path ());
rdb::Category *this_cat = db.category_by_name_non_const (cat.path ());
if (! this_cat && create_missing) {
this_cat = db.create_category (parent, cat.name ());
this_cat->set_description (cat.description ());
}
if (this_cat) {
cat2cat.insert (std::make_pair (this_cat->id (), cat.id ()));
rev_cat2cat.insert (std::make_pair (cat.id (), this_cat->id ()));
}
for (auto c = cat.sub_categories ().begin (); c != cat.sub_categories ().end (); ++c) {
map_category (*c, db, cat2cat);
map_category (*c, db, cat2cat, rev_cat2cat, create_missing, this_cat);
}
}
void
Database::apply (const rdb::Database &other)
static void map_databases (rdb::Database &self, const rdb::Database &other,
std::map<id_type, id_type> &cell2cell,
std::map<id_type, id_type> &rev_cell2cell,
std::map<id_type, id_type> &cat2cat,
std::map<id_type, id_type> &rev_cat2cat,
std::map<id_type, id_type> &tag2tag,
std::map<id_type, id_type> &rev_tag2tag,
bool create_missing)
{
std::map<id_type, id_type> cell2cell;
std::map<id_type, id_type> cat2cat;
std::map<id_type, id_type> tag2tag;
std::map<id_type, id_type> rev_tag2tag;
for (auto c = other.cells ().begin (); c != other.cells ().end (); ++c) {
// TODO: do we have a consistent scheme of naming variants? What requirements
// exist towards detecting variant specific waivers
const rdb::Cell *this_cell = cell_by_qname (c->qname ());
rdb::Cell *this_cell = self.cell_by_qname_non_const (c->qname ());
if (! this_cell && create_missing) {
this_cell = self.create_cell (c->name (), c->variant (), c->layout_name ());
this_cell->import_references (c->references ());
}
if (this_cell) {
cell2cell.insert (std::make_pair (this_cell->id (), c->id ()));
rev_cell2cell.insert (std::make_pair (c->id (), this_cell->id ()));
}
}
for (auto c = other.categories ().begin (); c != other.categories ().end (); ++c) {
map_category (*c, *this, cat2cat);
map_category (*c, self, cat2cat, rev_cat2cat, create_missing, 0);
}
std::map<std::string, id_type> tags_by_name;
for (auto c = tags ().begin_tags (); c != tags ().end_tags (); ++c) {
for (auto c = self.tags ().begin_tags (); c != self.tags ().end_tags (); ++c) {
tags_by_name.insert (std::make_pair (c->name (), c->id ()));
}
@ -1923,8 +1948,25 @@ Database::apply (const rdb::Database &other)
if (t != tags_by_name.end ()) {
tag2tag.insert (std::make_pair (t->second, c->id ()));
rev_tag2tag.insert (std::make_pair (c->id (), t->second));
} else if (create_missing) {
auto tt = self.import_tag (*c);
tag2tag.insert (std::make_pair (tt.id (), c->id ()));
rev_tag2tag.insert (std::make_pair (c->id (), tt.id ()));
}
}
}
void
Database::apply (const rdb::Database &other)
{
std::map<id_type, id_type> cell2cell;
std::map<id_type, id_type> rev_cell2cell;
std::map<id_type, id_type> cat2cat;
std::map<id_type, id_type> rev_cat2cat;
std::map<id_type, id_type> tag2tag;
std::map<id_type, id_type> rev_tag2tag;
map_databases (*this, other, cell2cell, rev_cell2cell, cat2cat, rev_cat2cat, tag2tag, rev_tag2tag, false);
std::map<std::pair<id_type, id_type>, ValueMapEntry> value_map;
@ -1963,6 +2005,50 @@ Database::apply (const rdb::Database &other)
}
}
void
Database::merge (const Database &other)
{
if (top_cell_name () != other.top_cell_name ()) {
throw tl::Exception (tl::to_string (tr ("Merging of RDB databases requires identical top cell names")));
}
std::map<id_type, id_type> cell2cell;
std::map<id_type, id_type> rev_cell2cell;
std::map<id_type, id_type> cat2cat;
std::map<id_type, id_type> rev_cat2cat;
std::map<id_type, id_type> tag2tag;
std::map<id_type, id_type> rev_tag2tag;
map_databases (*this, other, cell2cell, rev_cell2cell, cat2cat, rev_cat2cat, tag2tag, rev_tag2tag, true);
for (Items::const_iterator i = other.items ().begin (); i != other.items ().end (); ++i) {
auto icell = rev_cell2cell.find (i->cell_id ());
if (icell == rev_cell2cell.end ()) {
continue;
}
auto icat = rev_cat2cat.find (i->category_id ());
if (icat == rev_cat2cat.end ()) {
continue;
}
rdb::Item *this_item = create_item (icell->second, icat->second);
this_item->set_values (i->values ());
this_item->set_multiplicity (i->multiplicity ());
this_item->set_comment (i->comment ());
this_item->set_image_str (i->image_str ());
for (auto tt = rev_tag2tag.begin (); tt != rev_tag2tag.end (); ++tt) {
if (i->has_tag (tt->first)) {
this_item->add_tag (tt->second);
}
}
}
}
void
Database::scan_layout (const db::Layout &layout, db::cell_index_type cell_index, const std::vector<std::pair<unsigned int, std::string> > &layers_and_descriptions, bool flat)
{

View File

@ -1987,6 +1987,11 @@ public:
*/
void import_tag (const Tag &tag);
/**
* @brief Import a tag and returns the reference for the new tag
*/
Tag &import_tag_with_ref (const Tag &tag);
/**
* @brief Clear the collection of tags
*/
@ -2143,6 +2148,11 @@ public:
*/
void import_tags (const Tags &tags);
/**
* @brief Import a tag
*/
Tag &import_tag (const Tag &tag);
/**
* @brief Get the reference to the categories collection (const version)
*/
@ -2478,6 +2488,17 @@ public:
*/
void apply (const rdb::Database &other);
/**
* @brief Merges another database to this one
*
* Merging requires the other database to have the same top cell.
* Merging involves:
* * categories present in other, but not in *this will be created (based on category name and parent category)
* * cells present in other, but not in *this will be created (based on cell name and variant)
* * items will be transferred from other to *this based on cell and category
*/
void merge (const rdb::Database &other);
/**
* @brief Scans a layout into this RDB
*

View File

@ -822,3 +822,250 @@ TEST(13_ApplyIgnoreUnknownTag)
EXPECT_EQ (i1->tag_str (), "tag2");
}
TEST(20_MergeBasic)
{
rdb::Database db1;
db1.set_top_cell_name ("A");
rdb::Database db2;
db1.set_top_cell_name ("B");
try {
// can't merge DB's with different top cell names
db1.merge (db2);
EXPECT_EQ (0, 1);
} catch (...) { }
db1.set_top_cell_name ("TOP");
db2.set_top_cell_name ("TOP");
db1.merge (db2);
{
rdb::Cell *cell = db2.create_cell ("A", "VAR", "ALAY");
rdb::Category *pcat = db2.create_category ("PCAT");
rdb::Category *cat = db2.create_category (pcat, "CAT");
cat->set_description ("A child category");
// create two tags
/*auto t1_id =*/ db2.tags ().tag ("T1").id ();
auto t2_id = db2.tags ().tag ("T2", true).id ();
rdb::Item *item = db2.create_item (cell->id (), cat->id ());
item->set_comment ("Comment");
item->add_tag (t2_id);
item->set_image_str ("%nonsense%");
item->set_multiplicity (42);
item->add_value (db::DBox (0, 0, 1.0, 1.5));
item->add_value (42.0);
}
db1.merge (db2);
auto c = db1.cells ().begin ();
tl_assert (c != db1.cells ().end ());
const rdb::Cell *cell = c.operator-> ();
++c;
EXPECT_EQ (c == db1.cells ().end (), true);
EXPECT_EQ (cell->name (), "A");
EXPECT_EQ (cell->variant (), "VAR");
EXPECT_EQ (cell->layout_name (), "ALAY");
const rdb::Category *cat = db1.category_by_name ("PCAT.CAT");
tl_assert (cat != 0);
EXPECT_EQ (cat->name (), "CAT");
EXPECT_EQ (cat->path (), "PCAT.CAT");
EXPECT_EQ (cat->description (), "A child category");
EXPECT_EQ (cat->num_items (), size_t (1));
auto i = db1.items ().begin ();
tl_assert (i != db1.items ().end ());
const rdb::Item *item = i.operator-> ();
++i;
EXPECT_EQ (i == db1.items ().end (), true);
EXPECT_EQ (item->category_id (), cat->id ());
EXPECT_EQ (item->cell_id (), cell->id ());
EXPECT_EQ (item->comment (), "Comment");
EXPECT_EQ (item->multiplicity (), size_t (42));
EXPECT_EQ (item->has_image (), true);
EXPECT_EQ (item->image_str (), "%nonsense%")
EXPECT_EQ (item->values ().to_string (&db1), "box: (0,0;1,1.5);float: 42");
EXPECT_EQ (item->tag_str (), "#T2");
}
TEST(21_MergeCategories)
{
rdb::Database db1;
db1.set_top_cell_name ("TOP");
rdb::Database db2;
db2.set_top_cell_name ("TOP");
{
rdb::Category *pcat = db1.create_category ("PCAT");
pcat->set_description ("db1");
rdb::Category *cat = db1.create_category (pcat, "CAT");
cat->set_description ("db1");
}
{
rdb::Category *pcat = db2.create_category ("PCAT");
pcat->set_description ("db2a");
rdb::Category *cat2 = db2.create_category (pcat, "CAT2");
cat2->set_description ("db2a");
rdb::Category *pcat2 = db2.create_category ("PCAT2");
pcat2->set_description ("db2b");
rdb::Category *cat3 = db2.create_category (pcat2, "CAT3");
cat3->set_description ("db2b");
}
db1.merge (db2);
const rdb::Category *cat;
cat = db1.category_by_name ("PCAT");
tl_assert (cat != 0);
EXPECT_EQ (cat->name (), "PCAT");
EXPECT_EQ (cat->description (), "db1");
cat = db1.category_by_name ("PCAT2");
tl_assert (cat != 0);
EXPECT_EQ (cat->name (), "PCAT2");
EXPECT_EQ (cat->description (), "db2b");
cat = db1.category_by_name ("PCAT.CAT");
tl_assert (cat != 0);
EXPECT_EQ (cat->name (), "CAT");
EXPECT_EQ (cat->description (), "db1");
cat = db1.category_by_name ("PCAT.CAT2");
tl_assert (cat != 0);
EXPECT_EQ (cat->name (), "CAT2");
EXPECT_EQ (cat->description (), "db2a");
cat = db1.category_by_name ("PCAT2.CAT3");
tl_assert (cat != 0);
EXPECT_EQ (cat->name (), "CAT3");
EXPECT_EQ (cat->description (), "db2b");
int ncat = 0;
for (auto c = db1.categories ().begin (); c != db1.categories ().end (); ++c) {
++ncat;
for (auto s = c->sub_categories ().begin (); s != c->sub_categories ().end (); ++s) {
++ncat;
}
}
EXPECT_EQ (ncat, 5);
}
TEST(22_MergeCells)
{
rdb::Database db1;
db1.set_top_cell_name ("TOP");
rdb::Database db2;
db2.set_top_cell_name ("TOP");
{
rdb::Cell *parent;
parent = db1.create_cell ("TOP");
rdb::Cell *cell;
cell = db1.create_cell ("A");
cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 2.0)), parent->id ()));
cell = db1.create_cell ("A", "VAR1", "ALAY");
cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, -2.0)), parent->id ()));
}
{
rdb::Cell *parent;
parent = db2.create_cell ("TOP");
rdb::Cell *cell;
cell = db2.create_cell ("B");
cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 0.0)), parent->id ()));
cell = db2.create_cell ("A");
cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, 2.5)), parent->id ())); // reference not taken!
cell = db2.create_cell ("A", "VAR2", "ALAY");
cell->references ().insert (rdb::Reference (db::DCplxTrans (db::DVector (1.0, -1.0)), parent->id ()));
}
db1.merge (db2);
std::set<std::string> cells;
for (auto c = db1.cells ().begin (); c != db1.cells ().end (); ++c) {
if (c->references ().begin () != c->references ().end ()) {
cells.insert (c->qname () + "[" + c->references ().begin ()->trans_str () + "]");
} else {
cells.insert (c->qname ());
}
}
EXPECT_EQ (tl::join (cells.begin (), cells.end (), ";"), "A:1[r0 *1 1,2];A:VAR1[r0 *1 1,-2];A:VAR2[r0 *1 1,-1];B[r0 *1 1,0];TOP");
}
TEST(23_MergeTags)
{
rdb::Database db1;
db1.set_top_cell_name ("TOP");
rdb::Database db2;
db2.set_top_cell_name ("TOP");
db1.tags ().tag ("T1");
db1.tags ().tag ("T2");
db2.tags ().tag ("T1");
db2.tags ().tag ("T3", true);
db1.merge (db2);
std::set<std::string> tags;
for (auto t = db1.tags ().begin_tags (); t != db1.tags ().end_tags (); ++t) {
tags.insert (t->is_user_tag () ? "#" + t->name () : t->name ());
}
EXPECT_EQ (tl::join (tags.begin (), tags.end (), ";"), "#T3;T1;T2");
}
TEST(24_MergeItems)
{
rdb::Database db1;
db1.set_top_cell_name ("TOP");
rdb::Database db2;
db2.set_top_cell_name ("TOP");
{
rdb::Cell *cell = db1.create_cell ("TOP");
rdb::Category *cat1 = db1.create_category ("CAT1");
rdb::Category *cat2 = db1.create_category ("CAT2");
rdb::Item *item;
item = db1.create_item (cell->id (), cat1->id ());
item->set_comment ("db1a");
item = db1.create_item (cell->id (), cat2->id ());
item->set_comment ("db1b");
}
{
rdb::Cell *cell = db2.create_cell ("TOP");
rdb::Category *cat1 = db2.create_category ("CAT1");
rdb::Category *cat3 = db2.create_category ("CAT3");
rdb::Item *item;
item = db2.create_item (cell->id (), cat1->id ());
item->set_comment ("db2a");
item = db2.create_item (cell->id (), cat3->id ());
item->set_comment ("db2b");
}
db1.merge (db2);
std::set<std::string> items;
for (auto i = db1.items ().begin (); i != db1.items ().end (); ++i) {
const rdb::Item *item = i.operator-> ();
items.insert (item->cell_qname () + ":" + item->category_name () + "=" + item->comment ());
}
EXPECT_EQ (tl::join (items.begin (), items.end (), ";"), "TOP:CAT1=db1a;TOP:CAT1=db2a;TOP:CAT2=db1b;TOP:CAT3=db2b");
}

View File

@ -1,203 +0,0 @@
//----------------------------------------------------------------------------
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute
// this software, either in source code form or as a compiled binary, for any
// purpose, commercial or non-commercial, and by any means.
//
// In jurisdictions that recognize copyright laws, the author or authors of
// this software dedicate any and all copyright interest in the software to the
// public domain. We make this dedication for the benefit of the public at
// large and to the detriment of our heirs and successors. We intend this
// dedication to be an overt act of relinquishment in perpetuity of all present
// and future rights to this software under copyright law.
//
// 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 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.
//
// For more information, please refer to <http://unlicense.org/>
//-----------------------------------------------------------------------------
#ifndef ATOMIC_ATOMIC_H_
#define ATOMIC_ATOMIC_H_
// Macro for disallowing copying of an object.
#if __cplusplus >= 201103L
#define ATOMIC_DISALLOW_COPY(T) \
T(const T&) = delete; \
T& operator=(const T&) = delete;
#else
#define ATOMIC_DISALLOW_COPY(T) \
T(const T&); \
T& operator=(const T&);
#endif
// A portable static assert.
#if __cplusplus >= 201103L
#define ATOMIC_STATIC_ASSERT(condition, message) \
static_assert((condition), message)
#else
// Based on: http://stackoverflow.com/a/809465/5778708
#define ATOMIC_STATIC_ASSERT(condition, message) \
_impl_STATIC_ASSERT_LINE(condition, __LINE__)
#define _impl_PASTE(a, b) a##b
#ifdef __GNUC__
#define _impl_UNUSED __attribute__((__unused__))
#else
#define _impl_UNUSED
#endif
#define _impl_STATIC_ASSERT_LINE(condition, line) \
typedef char _impl_PASTE( \
STATIC_ASSERT_failed_, \
line)[(2 * static_cast<int>(!!(condition))) - 1] _impl_UNUSED
#endif
#if defined(__GNUC__) || defined(__clang__) || defined(__xlc__)
#define ATOMIC_USE_GCC_INTRINSICS
#elif defined(_MSC_VER)
#define ATOMIC_USE_MSVC_INTRINSICS
#include "atomic_msvc.h"
#elif __cplusplus >= 201103L
#define ATOMIC_USE_CPP11_ATOMIC
#include <atomic>
#else
#error Unsupported compiler / system.
#endif
namespace atomic {
template <typename T>
class atomic {
public:
ATOMIC_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 ||
sizeof(T) == 8,
"Only types of size 1, 2, 4 or 8 are supported");
atomic() : value_(static_cast<T>(0)) {}
explicit atomic(const T value) : value_(value) {}
/// @brief Performs an atomic increment operation (value + 1).
/// @returns The new value of the atomic object.
T operator++() {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
return __atomic_add_fetch(&value_, 1, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
return msvc::interlocked<T>::increment(&value_);
#else
return ++value_;
#endif
}
/// @brief Performs an atomic decrement operation (value - 1).
/// @returns The new value of the atomic object.
T operator--() {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
return __atomic_sub_fetch(&value_, 1, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
return msvc::interlocked<T>::decrement(&value_);
#else
return --value_;
#endif
}
/// @brief Performs an atomic compare-and-swap (CAS) operation.
///
/// The value of the atomic object is only updated to the new value if the
/// old value of the atomic object matches @c expected_val.
///
/// @param expected_val The expected value of the atomic object.
/// @param new_val The new value to write to the atomic object.
/// @returns True if new_value was written to the atomic object.
bool compare_exchange(const T expected_val, const T new_val) {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
T e = expected_val;
return __atomic_compare_exchange_n(
&value_, &e, new_val, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
const T old_val =
msvc::interlocked<T>::compare_exchange(&value_, new_val, expected_val);
return (old_val == expected_val);
#else
T e = expected_val;
return value_.compare_exchange_weak(e, new_val);
#endif
}
/// @brief Performs an atomic set operation.
///
/// The value of the atomic object is unconditionally updated to the new
/// value.
///
/// @param new_val The new value to write to the atomic object.
void store(const T new_val) {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
__atomic_store_n(&value_, new_val, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
(void)msvc::interlocked<T>::exchange(&value_, new_val);
#else
value_.store(new_val);
#endif
}
/// @returns the current value of the atomic object.
/// @note Be careful about how this is used, since any operations on the
/// returned value are inherently non-atomic.
T load() const {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
return __atomic_load_n(&value_, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
// TODO(m): Is there a better solution for MSVC?
return value_;
#else
return value_;
#endif
}
/// @brief Performs an atomic exchange operation.
///
/// The value of the atomic object is unconditionally updated to the new
/// value, and the old value is returned.
///
/// @param new_val The new value to write to the atomic object.
/// @returns the old value.
T exchange(const T new_val) {
#if defined(ATOMIC_USE_GCC_INTRINSICS)
return __atomic_exchange_n(&value_, new_val, __ATOMIC_SEQ_CST);
#elif defined(ATOMIC_USE_MSVC_INTRINSICS)
return msvc::interlocked<T>::exchange(&value_, new_val);
#else
return value_.exchange(new_val);
#endif
}
T operator=(const T new_value) {
store(new_value);
return new_value;
}
operator T() const {
return load();
}
private:
#if defined(ATOMIC_USE_GCC_INTRINSICS) || defined(ATOMIC_USE_MSVC_INTRINSICS)
volatile T value_;
#else
std::atomic<T> value_;
#endif
ATOMIC_DISALLOW_COPY(atomic)
};
} // namespace atomic
// Undef temporary defines.
#undef ATOMIC_USE_GCC_INTRINSICS
#undef ATOMIC_USE_MSVC_INTRINSICS
#undef ATOMIC_USE_CPP11_ATOMIC
#endif // ATOMIC_ATOMIC_H_

View File

@ -1,238 +0,0 @@
//-----------------------------------------------------------------------------
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute
// this software, either in source code form or as a compiled binary, for any
// purpose, commercial or non-commercial, and by any means.
//
// In jurisdictions that recognize copyright laws, the author or authors of
// this software dedicate any and all copyright interest in the software to the
// public domain. We make this dedication for the benefit of the public at
// large and to the detriment of our heirs and successors. We intend this
// dedication to be an overt act of relinquishment in perpetuity of all present
// and future rights to this software under copyright law.
//
// 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 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.
//
// For more information, please refer to <http://unlicense.org/>
//-----------------------------------------------------------------------------
#ifndef ATOMIC_ATOMIC_MSVC_H_
#define ATOMIC_ATOMIC_MSVC_H_
// Define which functions we need (don't include <intrin.h>).
extern "C" {
short _InterlockedIncrement16(short volatile*);
long _InterlockedIncrement(long volatile*);
__int64 _InterlockedIncrement64(__int64 volatile*);
short _InterlockedDecrement16(short volatile*);
long _InterlockedDecrement(long volatile*);
__int64 _InterlockedDecrement64(__int64 volatile*);
char _InterlockedExchange8(char volatile*, char);
short _InterlockedExchange16(short volatile*, short);
long __cdecl _InterlockedExchange(long volatile*, long);
__int64 _InterlockedExchange64(__int64 volatile*, __int64);
char _InterlockedCompareExchange8(char volatile*, char, char);
short _InterlockedCompareExchange16(short volatile*, short, short);
long __cdecl _InterlockedCompareExchange(long volatile*, long, long);
__int64 _InterlockedCompareExchange64(__int64 volatile*, __int64, __int64);
};
// Define which functions we want to use as inline intriniscs.
#pragma intrinsic(_InterlockedIncrement)
#pragma intrinsic(_InterlockedIncrement16)
#pragma intrinsic(_InterlockedDecrement)
#pragma intrinsic(_InterlockedDecrement16)
#pragma intrinsic(_InterlockedCompareExchange)
#pragma intrinsic(_InterlockedCompareExchange8)
#pragma intrinsic(_InterlockedCompareExchange16)
#pragma intrinsic(_InterlockedExchange)
#pragma intrinsic(_InterlockedExchange8)
#pragma intrinsic(_InterlockedExchange16)
#if defined(_M_X64)
#pragma intrinsic(_InterlockedIncrement64)
#pragma intrinsic(_InterlockedDecrement64)
#pragma intrinsic(_InterlockedCompareExchange64)
#pragma intrinsic(_InterlockedExchange64)
#endif // _M_X64
namespace atomic {
namespace msvc {
template <typename T, size_t N = sizeof(T)>
struct interlocked {
};
template <typename T>
struct interlocked<T, 1> {
static inline T increment(T volatile* x) {
// There's no _InterlockedIncrement8().
char old_val, new_val;
do {
old_val = static_cast<char>(*x);
new_val = old_val + static_cast<char>(1);
} while (_InterlockedCompareExchange8(reinterpret_cast<volatile char*>(x),
new_val,
old_val) != old_val);
return static_cast<T>(new_val);
}
static inline T decrement(T volatile* x) {
// There's no _InterlockedDecrement8().
char old_val, new_val;
do {
old_val = static_cast<char>(*x);
new_val = old_val - static_cast<char>(1);
} while (_InterlockedCompareExchange8(reinterpret_cast<volatile char*>(x),
new_val,
old_val) != old_val);
return static_cast<T>(new_val);
}
static inline T compare_exchange(T volatile* x,
const T new_val,
const T expected_val) {
return static_cast<T>(
_InterlockedCompareExchange8(reinterpret_cast<volatile char*>(x),
static_cast<const char>(new_val),
static_cast<const char>(expected_val)));
}
static inline T exchange(T volatile* x, const T new_val) {
return static_cast<T>(_InterlockedExchange8(
reinterpret_cast<volatile char*>(x), static_cast<const char>(new_val)));
}
};
template <typename T>
struct interlocked<T, 2> {
static inline T increment(T volatile* x) {
return static_cast<T>(
_InterlockedIncrement16(reinterpret_cast<volatile short*>(x)));
}
static inline T decrement(T volatile* x) {
return static_cast<T>(
_InterlockedDecrement16(reinterpret_cast<volatile short*>(x)));
}
static inline T compare_exchange(T volatile* x,
const T new_val,
const T expected_val) {
return static_cast<T>(
_InterlockedCompareExchange16(reinterpret_cast<volatile short*>(x),
static_cast<const short>(new_val),
static_cast<const short>(expected_val)));
}
static inline T exchange(T volatile* x, const T new_val) {
return static_cast<T>(
_InterlockedExchange16(reinterpret_cast<volatile short*>(x),
static_cast<const short>(new_val)));
}
};
template <typename T>
struct interlocked<T, 4> {
static inline T increment(T volatile* x) {
return static_cast<T>(
_InterlockedIncrement(reinterpret_cast<volatile long*>(x)));
}
static inline T decrement(T volatile* x) {
return static_cast<T>(
_InterlockedDecrement(reinterpret_cast<volatile long*>(x)));
}
static inline T compare_exchange(T volatile* x,
const T new_val,
const T expected_val) {
return static_cast<T>(
_InterlockedCompareExchange(reinterpret_cast<volatile long*>(x),
static_cast<const long>(new_val),
static_cast<const long>(expected_val)));
}
static inline T exchange(T volatile* x, const T new_val) {
return static_cast<T>(_InterlockedExchange(
reinterpret_cast<volatile long*>(x), static_cast<const long>(new_val)));
}
};
template <typename T>
struct interlocked<T, 8> {
static inline T increment(T volatile* x) {
#if defined(_M_X64)
return static_cast<T>(
_InterlockedIncrement64(reinterpret_cast<volatile __int64*>(x)));
#else
// There's no _InterlockedIncrement64() for 32-bit x86.
__int64 old_val, new_val;
do {
old_val = static_cast<__int64>(*x);
new_val = old_val + static_cast<__int64>(1);
} while (_InterlockedCompareExchange64(
reinterpret_cast<volatile __int64*>(x), new_val, old_val) !=
old_val);
return static_cast<T>(new_val);
#endif // _M_X64
}
static inline T decrement(T volatile* x) {
#if defined(_M_X64)
return static_cast<T>(
_InterlockedDecrement64(reinterpret_cast<volatile __int64*>(x)));
#else
// There's no _InterlockedDecrement64() for 32-bit x86.
__int64 old_val, new_val;
do {
old_val = static_cast<__int64>(*x);
new_val = old_val - static_cast<__int64>(1);
} while (_InterlockedCompareExchange64(
reinterpret_cast<volatile __int64*>(x), new_val, old_val) !=
old_val);
return static_cast<T>(new_val);
#endif // _M_X64
}
static inline T compare_exchange(T volatile* x,
const T new_val,
const T expected_val) {
return static_cast<T>(_InterlockedCompareExchange64(
reinterpret_cast<volatile __int64*>(x),
static_cast<const __int64>(new_val),
static_cast<const __int64>(expected_val)));
}
static inline T exchange(T volatile* x, const T new_val) {
#if defined(_M_X64)
return static_cast<T>(
_InterlockedExchange64(reinterpret_cast<volatile __int64*>(x),
static_cast<const __int64>(new_val)));
#else
// There's no _InterlockedExchange64 for 32-bit x86.
__int64 old_val;
do {
old_val = static_cast<__int64>(*x);
} while (_InterlockedCompareExchange64(
reinterpret_cast<volatile __int64*>(x), new_val, old_val) !=
old_val);
return static_cast<T>(old_val);
#endif // _M_X64
}
};
} // namespace msvc
} // namespace atomic
#endif // ATOMIC_ATOMIC_MSVC_H_

View File

@ -1,78 +0,0 @@
//-----------------------------------------------------------------------------
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute
// this software, either in source code form or as a compiled binary, for any
// purpose, commercial or non-commercial, and by any means.
//
// In jurisdictions that recognize copyright laws, the author or authors of
// this software dedicate any and all copyright interest in the software to the
// public domain. We make this dedication for the benefit of the public at
// large and to the detriment of our heirs and successors. We intend this
// dedication to be an overt act of relinquishment in perpetuity of all present
// and future rights to this software under copyright law.
//
// 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 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.
//
// For more information, please refer to <http://unlicense.org/>
//-----------------------------------------------------------------------------
#ifndef ATOMIC_SPINLOCK_H_
#define ATOMIC_SPINLOCK_H_
#include "atomic.h"
namespace atomic {
class spinlock {
public:
spinlock() : value_(0) {}
/// @brief Acquire the lock (blocking).
/// @note Trying to acquire a lock that is already held by the calling thread
/// will dead-lock (block indefinitely).
void lock() {
while (!value_.compare_exchange(UNLOCKED, LOCKED))
;
}
/// @brief Release the lock.
/// @note It is an error to release a lock that has not been previously
/// acquired.
void unlock() { value_.store(UNLOCKED); }
private:
static const int UNLOCKED = 0;
static const int LOCKED = 1;
atomic<int> value_;
ATOMIC_DISALLOW_COPY(spinlock)
};
class lock_guard {
public:
/// @brief The constructor acquires the lock.
/// @param lock The spinlock that will be locked.
explicit lock_guard(spinlock& lock) : lock_(lock) {
lock_.lock();
}
/// @brief The destructor releases the lock.
~lock_guard() {
lock_.unlock();
}
private:
spinlock& lock_;
ATOMIC_DISALLOW_COPY(lock_guard)
};
} // namespace atomic
#endif // ATOMIC_SPINLOCK_H_

View File

@ -217,7 +217,7 @@ public:
/**
* @brief Constructor
*/
Registrar <X> ()
Registrar ()
: mp_first (0)
{
// .. nothing yet ..

View File

@ -175,13 +175,13 @@ public:
typedef typename receivers::iterator receivers_iterator;
#endif
event<_TMPLARGLISTP> ()
event ()
: mp_destroyed_sentinel (0)
{
// .. nothing yet ..
}
~event<_TMPLARGLISTP> ()
~event ()
{
if (mp_destroyed_sentinel) {
*mp_destroyed_sentinel = true;

View File

@ -412,9 +412,9 @@ JobBase::stop ()
m_stopping = false;
m_running = false;
m_lock.unlock ();
stopped ();
m_lock.unlock ();
}
void

View File

@ -20,15 +20,17 @@
*/
#if !defined(HAVE_QT) || defined(HAVE_PTHREADS)
#include "tlThreads.h"
#include "tlUtils.h"
#include "tlTimer.h"
#include "tlSleep.h"
#include "tlLog.h"
#include "tlInternational.h"
#include <map>
#if !defined(HAVE_QT) || defined(HAVE_PTHREADS)
#define _TIMESPEC_DEFINED // avoids errors with pthread-win and MSVC2017
#include <pthread.h>
#include <errno.h>
@ -39,12 +41,71 @@
# include <unistd.h>
#endif
#endif
namespace tl
{
// -------------------------------------------------------------------------------
// WaitCondition implementation
#if defined(HAVE_CPP20) || !defined(HAVE_QT) || defined(HAVE_PTHREADS)
#if defined(HAVE_CPP20)
class WaitConditionPrivate
{
public:
WaitConditionPrivate ()
: m_condition ()
{
}
~WaitConditionPrivate ()
{
}
bool wait (Mutex *mutex, unsigned long time)
{
bool result = true;
m_condition.clear (std::memory_order_release);
mutex->unlock ();
if (time != std::numeric_limits<unsigned long>::max ()) {
while (time > 0 && ! m_condition.test (std::memory_order_acquire)) {
tl::usleep (1000);
--time;
}
result = (time != 0);
} else {
m_condition.wait (false);
}
mutex->lock ();
return result;
}
void wake_all ()
{
if (! m_condition.test_and_set (std::memory_order_acquire)) {
m_condition.notify_all ();
}
}
void wake_one ()
{
if (! m_condition.test_and_set (std::memory_order_acquire)) {
m_condition.notify_one ();
}
}
private:
std::atomic_flag m_condition;
};
#else
class WaitConditionPrivate
{
@ -139,6 +200,8 @@ private:
bool m_initialized;
};
#endif
WaitCondition::WaitCondition ()
{
mp_data = new WaitConditionPrivate ();
@ -165,9 +228,13 @@ void WaitCondition::wakeOne ()
mp_data->wake_one ();
}
#endif
// -------------------------------------------------------------------------------
// Thread implementation
#if !defined(HAVE_QT) || defined(HAVE_PTHREADS)
class ThreadPrivateData
{
public:
@ -401,6 +468,6 @@ ThreadStorageHolderBase *ThreadStorageBase::holder ()
}
}
}
#endif
}

View File

@ -33,8 +33,7 @@
# include <QThread>
# include <QThreadStorage>
#else
// atomics taken from https://github.com/mbitsnbites/atomic
# include "atomic/spinlock.h"
# include <atomic>
#endif
namespace tl
@ -60,11 +59,21 @@ public:
class TL_PUBLIC Mutex
{
public:
Mutex () : m_spinlock () { }
void lock () { m_spinlock.lock (); }
void unlock () { m_spinlock.unlock (); }
Mutex () { }
void lock ()
{
while (flag.test_and_set (std::memory_order_acquire))
;
}
void unlock ()
{
flag.clear (std::memory_order_release);
}
private:
atomic::spinlock m_spinlock;
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};
#endif
@ -75,7 +84,7 @@ private:
* available.
*/
#if defined(HAVE_QT) && !defined(HAVE_PTHREADS)
#if defined(HAVE_QT) && !defined(HAVE_PTHREADS) && !defined(HAVE_CPP20)
class TL_PUBLIC WaitCondition
: public QWaitCondition

View File

@ -61,6 +61,26 @@ class DBShapesTest(unittest.TestCase):
shapes = top.shapes(l1)
self.assertEqual(sys.getrefcount(shapes), 2)
# Tests the ability to take PolygonWithProperties instead of base class
# for setter
def test_3(self):
shapes = pya.Shapes()
shapes.insert(pya.Polygon(pya.Box(0, 0, 100, 200)))
self.assertEqual(";".join([ str(s) for s in shapes.each() ]), "polygon (0,0;0,200;100,200;100,0)")
pwp = pya.PolygonWithProperties(pya.Polygon(pya.Box(0, 0, 110, 210)))
s0 = [ s for s in shapes.each() ][0]
s0.polygon = pwp
self.assertEqual(";".join([ str(s) for s in shapes.each() ]), "polygon (0,0;0,210;110,210;110,0)")
# with object substitution
s0.polygon = ( pya.Box(0, 0, 120, 220) )
self.assertEqual(";".join([ str(s) for s in shapes.each() ]), "polygon (0,0;0,220;120,220;120,0)")
# with object substitution by constructor inference
s0.polygon = pya.Box(0, 0, 130, 230)
self.assertEqual(";".join([ str(s) for s in shapes.each() ]), "polygon (0,0;0,230;130,230;130,0)")
# run unit tests
if __name__ == '__main__':

View File

@ -1205,6 +1205,77 @@ class RDB_TestClass < TestBase
end
# apply
def test_16
rdb1 = RBA::ReportDatabase::new
cat = rdb1.create_category("CAT")
cell = rdb1.create_cell("TOP")
item = rdb1.create_item(cell, cat)
item.add_value("item1")
item.add_tag(rdb1.tag_id("t1"))
assert_equal(item.tags_str, "t1")
rdb2 = RBA::ReportDatabase::new
cat = rdb2.create_category("CAT")
cell = rdb2.create_cell("TOP")
item = rdb2.create_item(cell, cat)
item.add_value("item1")
item.add_tag(rdb2.tag_id("t2"))
assert_equal(item.tags_str, "t2")
items = rdb1.each_item.to_a
assert_equal(items.size, 1)
assert_equal(items[0].tags_str, "t1")
rdb1.apply(rdb2)
items = rdb1.each_item.to_a
assert_equal(items.size, 1)
assert_equal(items[0].tags_str, "t2")
end
# merge
def test_17
rdb1 = RBA::ReportDatabase::new
cat = rdb1.create_category("CAT")
cell = rdb1.create_cell("TOP")
item = rdb1.create_item(cell, cat)
item.add_value("item1")
item.add_tag(rdb1.tag_id("t1"))
assert_equal(item.tags_str, "t1")
rdb2 = RBA::ReportDatabase::new
cat = rdb2.create_category("CAT")
cell = rdb2.create_cell("TOP")
item = rdb2.create_item(cell, cat)
item.add_value("item1")
item.add_tag(rdb2.tag_id("t2"))
assert_equal(item.tags_str, "t2")
items = rdb1.each_item.to_a
assert_equal(items.size, 1)
assert_equal(items[0].tags_str, "t1")
rdb1.merge(rdb2)
items = rdb1.each_item.to_a
assert_equal(items.size, 2)
assert_equal(items[0].tags_str, "t1")
assert_equal(items[1].tags_str, "t2")
end
end
load("test_epilogue.rb")