Skip to content

Hypergraph-based system execution scheduler. - #8

Draft
Kuba663 wants to merge 17 commits into
CleanroomMC:mainfrom
Kuba663:devel/scheduler
Draft

Hypergraph-based system execution scheduler.#8
Kuba663 wants to merge 17 commits into
CleanroomMC:mainfrom
Kuba663:devel/scheduler

Conversation

@Kuba663

@Kuba663 Kuba663 commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

In this PR, I implemented a system scheduler that automatically decides which tasks can be ran in parallel.
The Scheduler works as follows:

  1. A hypergraph is created, where the systems are vertices and their dependencies (components and resources) are edges.
  2. A graph is created where each vertex is connected to all of the vertices that do not share an edge in the hypergraph.
  3. The resulting disjointed graph is colored.
  4. Vertices of each color are added topriority queues (one for each color).
  5. The priority queue array is used to build a directed acyclic graph (this is not complete yet).

TODO:

  • Make the Scheduler actually build the DAG.
  • Write a more efficient Hypergraph implementation, the current one uses two multimaps.
  • Handle systems deending on eachother.
  • Use a Fibonacci Heap in the graph coloring function as it's faster.
  • Refactor the coding style, as it's not appropriate.
  • Write Unit Tests for Hypergraphs and Graph Coloring.
  • Integrate into Kirino, currently it's just a thing that exists and does nothing.

@Kuba663

Kuba663 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

I'm giving up on the Fibonacci Heap for now as I don't know how to implement removal without key decreases.

@Kuba663

Kuba663 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

Bottleneck related to systems depending on eachother, currently the scheduler handles them by simply treating them as sharing a hyperedge, however this can potentially cause problems in the coloring part, as it's not guaranteed that depending systems will have the same color. A possible solution would be collapsing dependent vertex chains into single vertices representing sequential execution of several systems. This however raises another problem related to the vertices in two different branches that are required for executing another system sharing an hyperedgeedge, which could cause potential race conditions. The solution would require creating a separate system for dealing with dependencies that treats system/system relation differently from the typical system/resource relation.
Presented below is an ilustration showcasing the problem. The vertices cannot be collapsed due to a shared hyperedge representing a shared read-write/write-write to a resource (that is: a Resource or a Component).
image
Yet another alternative solution to this problem would be collapsing all vertices under the same hyperedges into a single vertex, until only vertices representing systems completely independent of eachother remain.

@tttsaurus

Copy link
Copy Markdown
Member

I'm not sure if I fully grasp your modeling. Let me illustrate what I read from your wording first.

1 Build Hypergraph

Let's say

System A accesses Position
System B accesses Position
System C accesses Velocity

so hyperedges are

H1 (Position): { A, B }
H2 (Velocity): { C }

I deliberately ignore the granularity here, like read or write or their actual component access chain position.xyz.x or whatever

So the systems sharing a hyperedge may conflict.

2 Build Another Graph To Represent The Compatibility

Based on

H1: { A, B }
H2: { C }
  • For A
    • A and B shares H1, don't connect A to B
    • A and C shares no edge, connect A to C
  • For B
    • B and A shares H1, don't connect B to A
    • B and C shares no edge, connect B to C
  • For C
    • C and A shares no edge, connect C to A
    • C and B shares no edge, connect C to B

As a result, we got

graph TD
    A <--> C
    B <--> C
Loading

No need to be directed. I simply can't remove the arrow.

And based on what you said

The resulting disjointed graph is colored.

I think the resulting graph isn't necessarily disjoint and I just created a counterexample.

connected to all of the vertices that do not share an edge in the hypergraph

Therefore, the resulting graph is a compatibility graph instead of a conflict graph.
Thus, the vertices that share the same color can't run together.

3 Compatibility Graph Coloring

graph TD
    A[Red] <--> C[Blue]
    B[Red] <--> C[Blue]
Loading

4 Add To Priority Queues

Q1: A[Red], B[Red]
Q2: C[Blue]

And the priority is set by the user right? Let's say A is greater than B here.

Just a quick check, B runs after A, which is correct as both of them wants Position, and Q1 and Q2 can be parallelized.

5 Build The DAG (aka SystemExeFlowGraph)

graph TD
    S[Start] --> A
    S --> C
    A --> B
    C --> E[End]
    B --> E
Loading

6 Your Previous Comment

Bottleneck related to systems depending on eachother, currently the scheduler handles them by simply treating them as sharing a hyperedge, however this can potentially cause problems in the coloring part

I'm confused about why systems need to depend on each other, like why do we want to add dependencies like A --> B explicitly to the graph? Isn't the priority handled by the priority queues, like users manually input the priority beforehand?

Even if you want the capability to declare dependencies like depends(A, B), you can maintain a DAG and compute the corresponding priority for the users. Just like a facade layer.

7 Granularity

Btw, the resource (or component) declaration is definitely too coarse at the moment. What I want is like

ComponentA:
  field1.field2 | read
  fieldX.fieldY | write

ComponentB:
  xyz | write

And there's definitely a way that you don't have to refactor too much, like doing it in a facade way.
Specifically, you can keep this part

System A accesses Position
System B accesses Position
System C accesses Velocity

But, treat Position, Velocity like constraint identifiers instead of the actual component resource representation.
Then, you generate these identifiers from the actual component resource representation:

System A
  Component Position:
    xyz.x | read
System B
  Component Position:
    xyz.y | write
System C
  Component Velocity:
    xyz | read

This is where the actual read/read, write/write, read/write analysis happens. Obviously, A doesn't conflict B;
A,B doesn't conflict C as well.

So the generated constraint identifiers will be:

System A accesses PositionStub0
System B accesses PositionStub1
System C accesses VelocityStub0

As a result, there will be three hyperedges, causing no conflict as needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants