Support basic graph

master
Marko Semet 2019-08-01 01:28:04 +02:00
parent 20ea99560b
commit 004390d3c9
2 changed files with 409 additions and 3 deletions

View File

@ -3,6 +3,7 @@
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <sirEdit/data/fields.hpp>
#include <sirEdit/data/types.hpp>
@ -475,5 +476,144 @@ namespace sirEdit::data {
}
return result;
}
/**
* Check if tool is valid.
* @param is a valid tool specification
*/
bool isValid() const {
// List types and fields
std::unordered_set<const Type*> types;
std::unordered_set<const Field*> fields;
{
std::unordered_set<const Type*> todoTypes;
std::unordered_set<const Field*> todoFields;
for(auto& i : this->__statesType) // Add all set types
todoTypes.insert(i.first);
for(auto& i : this->__statesFields) // Add all types that set a field
for(auto& j : i.second)
todoTypes.insert(j.first);
while(todoTypes.size() > 0 || todoFields.size() > 0) { // Run as long as a type or field has to be processed
while(todoTypes.size() > 0) { // Add missing types
const Type* tmp;
{
auto tmp2 = todoTypes.begin();
tmp = *tmp2;
todoTypes.erase(tmp2);
}
if(types.find(tmp) == types.end()) { // Add missing type
types.insert(tmp);
{ // Add super type
const Type* super = getSuper(*tmp);
if(super != nullptr)
if(types.find(super) == types.end())
todoTypes.insert(super);
}
{ // Add interfaces
auto& interfaces = getInterfaces(*tmp);
for(const Type* i : interfaces)
if(types.find(i) == types.end())
todoTypes.insert(i);
}
{ // Find fields
auto& tmpFields = getFields(*tmp);
for(const Field& i : tmpFields)
if(fields.find(&i) == fields.end())
todoFields.insert(&i);
}
}
}
while(todoFields.size() > 0) { // Add missing fields
const Field* tmp;
{
auto tmp2 = todoFields.begin();
tmp = *tmp2;
todoFields.erase(tmp2);
}
while(tmp != nullptr) {
if(fields.find(tmp) == fields.end()) { // Add missing field
fields.insert(tmp);
for(const Type* i : tmp->getType().types) // Add types
if(types.find(i) == types.end())
todoTypes.insert(i);
}
tmp = tmp->getMeta().view;
}
}
}
}
// Unset types
for(auto& i : this->__statesType)
if(std::get<1>(i.second) == TYPE_STATE::UNUSED)
if(this->getTypeTransitiveState(*(i.first)) >= TYPE_STATE::READ)
return false;
// Unset fields
for(auto& i : this->__statesFields)
for(auto& j : i.second)
if(j.second == FIELD_STATE::UNUSED)
if(this->getFieldTransitiveState(*(i.first)) >= FIELD_STATE::READ)
return false;
// Check coliding types
for(auto& i : types)
for(auto& j : i->getCollides())
if(this->getTypeTransitiveState(*i) >= TYPE_STATE::READ && this->getTypeTransitiveState(*j) >= TYPE_STATE::READ)
return false;
// Check coliding fields
for(auto& i : fields)
for(auto& j : i->getCollide())
if(this->getFieldTransitiveState(*i) >= FIELD_STATE::READ && this->getFieldTransitiveState(*j) >= FIELD_STATE::READ)
return false;
return true;
}
/**
* Generates a list of fields that at least readed but not created.
* @return fields that the tool needs
*/
std::unordered_set<const Field*> requiredFields() const {
std::unordered_set<const Field*> result;
for(auto& i : this->__statesFields) {
const Field* tmp = i.first;
while(tmp->getMeta().view != nullptr)
tmp = tmp->getMeta().view;
auto state = this->getFieldTransitiveState(*tmp);
if(state >= FIELD_STATE::READ && state != FIELD_STATE::CREATE)
result.insert(tmp);
}
return result;
}
/**
* Generates a list of fields that create the fields
* @return fields that the tool creates
*/
std::unordered_set<const Field*> generatedFields() const {
std::unordered_set<const Field*> result;
for(auto& i : this->__statesFields) {
const Field* tmp = i.first;
while(tmp->getMeta().view != nullptr)
tmp = tmp->getMeta().view;
if(this->getFieldTransitiveState(*tmp) == FIELD_STATE::CREATE)
result.insert(tmp);
}
return result;
}
};
}

View File

@ -2,9 +2,14 @@
#include <sirEdit/data/serialize.hpp>
#include <gtkmm.h>
#include <unordered_set>
#include <stdio.h>
#include <fstream>
#include <functional>
#include <iostream>
#include <sirEdit/main.hpp>
using namespace sirEdit;
using namespace sirEdit::data;
//
@ -107,15 +112,276 @@ class ImageRender : public Gtk::Widget
}
};
/**
* Check if subset is a subset of superset.
* @param subset the subset to check
* @param superset the source subset
* @return true if subset is a subset of a superset
*/
bool subset(const auto& subset, const auto& superset) {
for(auto& i : subset)
if(superset.find(i) == superset.end())
return false;
return true;
}
/**
* Bind tools for hashing and comparision
*/
struct ToolBinding {
Tool tool;
bool operator ==(const ToolBinding& tool) const {
if(tool.tool.getStatesFields().size() != this->tool.getStatesFields().size())
return false;
for(auto& i : tool.tool.getStatesFields())
if((tool.tool.getFieldTransitiveState(*(i.first)) >= FIELD_STATE::READ) != (this->tool.getFieldTransitiveState(*(i.first)) >= FIELD_STATE::READ))
return false;
return true;
}
};
template<>
struct std::hash<ToolBinding> {
size_t operator()(const ToolBinding& tool) const {
size_t result = 0;
std::hash<const Field*> hasher;
for(auto& i : tool.tool.getStatesFields())
result ^= hasher(i.first);
return result;
}
};
/**
* A Edge of a graph
*/
struct Edge {
ToolBinding from; /// Source state
const Tool* tool; /// Tool (edge info)
ToolBinding to; /// Target state
bool operator ==(const Edge& edge) const {
return this->tool == edge.tool && this->from == edge.from && this->to == edge.to;
}
};
template<>
struct std::hash<Edge> {
size_t operator()(const Edge& edge) const {
return std::hash<ToolBinding>()(edge.from) ^ std::hash<const Tool*>()(edge.tool) ^ std::hash<ToolBinding>()(edge.to);
}
};
class DependencieGraph : public Gtk::VBox {
private:
struct NodeInfo {
std::unordered_map<ToolBinding, std::unordered_set<const Tool*>> edges;
std::unordered_set<const Tool*> selfReferences;
bool isFirstNode;
};
ImageRender __renderer;
Gtk::ScrolledWindow __scrollable;
const Serializer& __serializer;
Gtk::Button __update;
std::unordered_set<Edge> singleChain(std::unordered_set<const Tool*> tools) {
std::unordered_set<ToolBinding> todo;
{ // argmin
const Tool* tmp = nullptr;
size_t counter = 0;
for(auto& i : tools) {
size_t iCounter = i->requiredFields().size();
if(tmp == nullptr) {
tmp = i;
counter = iCounter;
}
else if(counter > iCounter) {
tmp = i;
counter = iCounter;
}
}
if(tmp == nullptr)
return {};
else {
Tool tmpTool;
for(auto& i : tmp->requiredFields()) {
auto tmp2 = tmp->getStatesFields().find(i);
tmpTool.setFieldState(*(tmp2->second.begin()->first), *i, FIELD_STATE::READ);
}
todo.insert({tmpTool});
}
}
std::unordered_set<ToolBinding> done;
std::unordered_set<Edge> result;
while(todo.size() > 0) { // Tools todo
ToolBinding currentState;
{ // Remove any
auto tmp = todo.begin();
currentState = std::move(*tmp);
todo.erase(tmp);
}
done.insert(currentState);
for(auto& i : tools) {
if(subset(i->requiredFields(), currentState.tool.requiredFields())) {
Tool nextState = currentState.tool;
for(auto& j : i->generatedFields()) {
auto tmp = i->getStatesFields().find(j);
if(tmp == i->getStatesFields().end())
throw; // That should never happen
nextState.setFieldState(*(tmp->second.begin()->first), *j, FIELD_STATE::READ);
}
if(nextState.isValid()) {
result.insert({currentState, i, {nextState}});
if(done.find({nextState}) == done.end())
todo.insert({nextState});
}
}
}
}
return result;
}
std::unordered_set<Edge> chain(std::unordered_set<const Tool*> tools) {
std::unordered_set<Edge> result;
std::unordered_set<Edge> nextChain = singleChain(tools);
while(!subset(nextChain, result)) {
for(auto& i : nextChain) {
auto tmp = tools.find(i.tool);
if(tmp != tools.end())
tools.erase(tmp);
}
result.insert(nextChain.begin(), nextChain.end());
nextChain = singleChain(tools);
}
return result;
}
std::string genFile(auto statesSource, auto edgesSource) {
// Gen dot file
std::string file = tmpnam(nullptr);
{
// Generate output
std::ofstream output(file + ".gv", std::ios::binary | std::ios::out);
output << "digraph G {\n";
// Generate nodes
std::unordered_map<ToolBinding, size_t> states;
{
size_t counter = 0;
statesSource([&](const ToolBinding& state, bool isStart) -> void {
// Set data
states[state] = counter;
// Write node
output << "n" << counter << " [label=\"";
if(isStart) {
bool first = true;
for(auto& i : state.tool.getStatesFields())
if(state.tool.getFieldTransitiveState(*(i.first)) >= FIELD_STATE::READ) {
if(first)
first = false;
else
output << "\\n";
for(auto& j : i.second)
if(j.second >= FIELD_STATE::READ)
output << j.first->getName();
output << "::" << i.first->getName();
}
}
output << "\"];\n";
// Update counter
counter++;
});
}
// Generate edges
edgesSource([&](const ToolBinding& from, const std::unordered_set<const Tool*>& tools, const ToolBinding& to) -> void {
output << "n" << states[from] << " -> n" << states[to] << " [label=\"";
bool first = true;
for(auto& i : tools) {
if(first)
first = false;
else
output << "\\n";
output << i->getName();
}
output << "\"];\n";
});
output << "}";
output.close();
}
// Command
int result = runCommand({"dot", "-Tpng", file + ".gv", "-o", file + ".png"}, ".");
if(result != 0)
throw; // TODO: Remove
return file + ".png";
}
std::string showTools(const std::unordered_set<const Tool*>& tools) {
// Edges
auto edges = chain(tools);
// Convert representation
std::unordered_map<ToolBinding, NodeInfo> nodeInfo;
for(auto& i : edges) {
// Add from state
{
auto tmp = nodeInfo.find(i.from);
if(tmp == nodeInfo.end())
nodeInfo[i.from] = {{}, {}, true};
}
auto& tmp = nodeInfo[i.from];
if(i.from == i.to) // Self reference
tmp.selfReferences.insert(i.tool);
else { // Real edge
{ // Insert edge
auto tmp2 = tmp.edges.find(i.to);
if(tmp2 == tmp.edges.end())
tmp.edges.insert({i.to, {i.tool}});
else
tmp2->second.insert(i.tool);
}
{ // to state info
auto tmp2 = nodeInfo.find(i.to);
if(tmp2 == nodeInfo.end())
nodeInfo[i.to] = {{}, {}, false};
else
tmp2->second.isFirstNode = false;
}
}
}
// TODO: Remove doubles
// TODO: Remove unused created
// Draw and show
return this->genFile([&](auto func) -> void {
for(auto& i : nodeInfo)
func(i.first, i.second.isFirstNode);
}, [&](auto func) -> void {
for(auto& i : nodeInfo) {
for(auto& j : i.second.edges)
func(i.first, j.second, j.first);
if(i.second.selfReferences.size() > 0)
func(i.first, i.second.selfReferences, i.first);
}
});
}
public:
DependencieGraph() {
DependencieGraph(const Serializer& serializer) : __serializer(serializer) {
// Layout
this->__update = Gtk::Button("Update");
this->__scrollable.add(this->__renderer);
@ -124,7 +390,7 @@ class DependencieGraph : public Gtk::VBox {
// Button
this->__update.signal_clicked().connect([this]() -> void {
this->__renderer.loadImage("test.png"); // TODO: Calculate and draw correct diagram
this->__renderer.loadImage(showTools({this->__serializer.getTools().begin(), this->__serializer.getTools().end()}));
});
}
};
@ -989,7 +1255,7 @@ class Overview : public Gtk::VBox {
}
public:
Overview(Transactions& transactions) : Gtk::VBox(), transactions(transactions) {
Overview(Transactions& transactions) : Gtk::VBox(), transactions(transactions), graph(transactions.getData()) {
// Init Stack
this->stack.add(this->tool_paned, "Tool Overview", "Tool Overview");
this->stack.add(this->graph, "Tool Relations", "Tool Relations");