README.md

# GenGraph

GenGraph provides a way to build graph and tree data structures using GenServer-based nodes. Each node in a GenGraph is a GenServer process that can maintain edges to other nodes, enabling concurrent operations and automatic cleanup when processes terminate.

## Features

- **Process-based nodes**: Each node is a GenServer, enabling concurrent operations
- **Automatic cleanup**: Edges are automatically removed when connected processes die
- **Flexible relationships**: Support for weighted and bidirectional edges
- **Tree structures**: Specialized tree nodes with parent-child relationships and cycle prevention
- **GenObject integration**: Built on top of GenObject for enhanced process management
- **Monitor-based**: Uses process monitoring for robust edge management

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed by adding `gen_graph` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:gen_graph, "~> 0.1.1"}
  ]
end
```

## Quick Start

### Basic Graph Usage

```elixir
# Define a node module
defmodule MyNode do
  use GenGraph.Node, [
    data: nil,
    name: ""
  ]
end

# Create nodes
node1 = MyNode.new(data: "first", name: "node1")
node2 = MyNode.new(data: "second", name: "node2")
node3 = MyNode.new(data: "third", name: "node3")

# Add edges between nodes
node1 = MyNode.add_edge(node1, node2)
node1 = MyNode.add_edge(node1, node3, weight: 5)

# Create bidirectional edges
node2 = MyNode.add_edge(node2, node3, bidirectional: true)

# Check edges
IO.inspect(node1.edges)  # [{node2.pid, 0}, {node3.pid, 5}]
IO.inspect(node2.edges)  # [{node3.pid, 0}]
IO.inspect(node3.edges)  # [{node2.pid, 0}]
```

### Tree Usage

```elixir
# Define a tree node module
defmodule TreeNode do
  use GenGraph.Tree, [
    name: "",
    data: nil
  ]
end

# Create tree structure
root = TreeNode.new(name: "root")
child1 = TreeNode.new(name: "child1")
child2 = TreeNode.new(name: "child2")
grandchild = TreeNode.new(name: "grandchild")

# Build tree relationships
root = TreeNode.add_child(root, child1)
root = TreeNode.add_child(root, child2)
child1 = TreeNode.add_child(child1, grandchild)

# Check tree structure
IO.inspect(root.child_nodes)     # [child1.pid, child2.pid]
IO.inspect(child1.child_nodes)   # [grandchild.pid]
IO.inspect(TreeNode.get(grandchild, :parent_pid)) # child1.pid

# Cycle prevention - this will return :error
TreeNode.add_child(grandchild, root)  # :error
```

## Core Concepts

### Nodes

Every node in GenGraph is a GenServer process created using GenObject. Nodes maintain:

- **edges**: A list of `{pid, weight}` tuples representing outgoing connections
- **refs**: A map of monitor references to connected PIDs for automatic cleanup
- **Custom fields**: Any additional data defined when using the module

### Edges

Edges represent connections between nodes and support:

- **Weights**: Numeric values associated with edges (default: 0)
- **Bidirectional**: Edges can be created in both directions simultaneously
- **Automatic cleanup**: Edges are removed when target processes terminate

### Process Monitoring

GenGraph uses Erlang's process monitoring to ensure data consistency:

- When an edge is created, the source node monitors the target node
- If a target node process dies, the edge is automatically removed
- Monitor references are properly cleaned up

## API Reference

### GenGraph.Node

#### `add_edge(from, to, opts \\ [])`

Adds an edge between two nodes with optional configuration.

**Options:**
- `:weight` - Numeric weight for the edge (default: 0)
- `:bidirectional` - Create edges in both directions (default: false)

**Examples:**
```elixir
# Simple edge
node1 = MyNode.add_edge(node1, node2)

# Weighted edge
node1 = MyNode.add_edge(node1, node2, weight: 5)

# Bidirectional edge
node1 = MyNode.add_edge(node1, node2, bidirectional: true)
```

#### `add_edge!(from, to, opts \\ [])`

Asynchronously adds an edge using GenServer.cast for better performance.

#### `remove_edge(from, to, opts \\ [])`

Removes an edge between two nodes.

**Options:**
- `:weight` - Specific weight of edge to remove (default: 0)
- `:bidirectional` - Remove edges in both directions (default: false)

#### `remove_edge!(from, to, opts \\ [])`

Asynchronously removes an edge using GenServer.cast.

### GenGraph.Tree

Tree nodes inherit all GenGraph.Node functionality plus:

#### `add_child(parent, child, opts \\ [])`

Adds a child node to a parent with cycle detection.

**Returns:** Updated parent struct or `:error` if operation would create a cycle.

#### `add_child!(parent, child, opts \\ [])`

Asynchronously adds a child (no cycle detection).

#### `remove_child(parent, child, opts \\ [])`

Removes a child node from a parent.

#### `remove_child!(parent, child, opts \\ [])`

Asynchronously removes a child node.

### GenServer Callbacks

Both `GenGraph.Node` and `GenGraph.Tree` implement GenServer callbacks that handle the internal messaging for edge management:

#### GenGraph.Node Callbacks

- `handle_call({:add_edge, node_pid, opts}, from, object)` - Handles synchronous edge addition
- `handle_call({:remove_edge, node_pid, opts}, from, object)` - Handles synchronous edge removal
- `handle_cast({:add_edge, node_pid, opts}, object)` - Handles asynchronous edge addition
- `handle_cast({:remove_edge, node_pid, opts}, object)` - Handles asynchronous edge removal
- `handle_info({:DOWN, ref, :process, pid, reason}, object)` - Handles process termination cleanup

#### GenGraph.Tree Callbacks

Tree nodes extend the Node callbacks with additional behavior:

- `handle_call({:add_edge, node_pid, opts}, from, object)` - Adds cycle detection and parent_pid management
- `handle_call({:remove_edge, node_pid, opts}, from, object)` - Clears parent_pid on removed children
- `handle_cast` variants - Similar extensions but without cycle detection
- `handle_info({:DOWN, ...}, object)` - Synchronizes child_nodes list after cleanup

#### Private Helper Functions

- `do_add_edge/3` - Internal function to add edges and monitor references
- `do_remove_edge/3` - Internal function to remove edges and clean up monitors
- `do_mirror_edges/2` (Tree only) - Synchronizes child_nodes with edges list
- `is_ancestor?/2` (Tree only) - Recursively checks for circular references

## Advanced Features

### Cycle Prevention

Tree nodes automatically prevent circular references:

```elixir
parent = TreeNode.new()
child = TreeNode.new()
grandchild = TreeNode.new()

parent = TreeNode.add_child(parent, child)
child = TreeNode.add_child(child, grandchild)

# This will return :error due to cycle detection
TreeNode.add_child(grandchild, parent)  # :error
```

### Automatic Cleanup

When a node process terminates, all edges pointing to it are automatically cleaned up:

```elixir
node1 = MyNode.new()
node2 = MyNode.new()

node1 = MyNode.add_edge(node1, node2)
IO.inspect(length(node1.edges))  # 1

# Kill node2
Process.exit(node2.pid, :kill)
:timer.sleep(10)

node1 = MyNode.get(node1)
IO.inspect(length(node1.edges))  # 0 - edge automatically removed
```

### Working with PIDs and Structs

All functions accept either PIDs or GenObject structs:

```elixir
# These are equivalent
MyNode.add_edge(node1, node2)
MyNode.add_edge(node1.pid, node2.pid)
MyNode.add_edge(node1, node2.pid)
MyNode.add_edge(node1.pid, node2)
```

## GenObject Integration

GenGraph is built on top of [GenObject](https://hex.pm/packages/gen_object), which provides:

- Enhanced GenServer functionality
- Struct-based process interaction
- Automatic PID management
- Built-in getter/setter methods

Each node automatically gets GenObject methods like:
- `new/1` - Create a new node process
- `get/1` - Retrieve current node state
- `get/2` - Get specific field value
- `put/3` - Set field value

## Testing

Run the test suite:

```bash
mix test
```

The test suite includes comprehensive tests for:
- Basic edge operations
- Bidirectional edges
- Weighted edges
- Automatic cleanup on process termination
- Tree operations and cycle prevention
- Parent-child relationship management

## License

This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.

## Contributing

1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass
5. Submit a pull request

## Acknowledgments

- Built by [DockYard](https://dockyard.com/phoenix-consulting)
- Uses [GenObject](https://hex.pm/packages/gen_object) for enhanced process management
- Inspired by graph theory and actor model patterns

Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) and published on [HexDocs](https://hexdocs.pm). Once published, the docs can be found at <https://hexdocs.pm/gen_graph>.