Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Disconnect incompatible types when port definition changes #120

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/slang/core/abstract/port-owner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ export abstract class PortOwner extends SlangNode {
this.streamPortOwner.initialize();
}

// tslint:disable-next-line:naming-convention
public get II(): PortModel {
return this.getPortIn()!;
}

// tslint:disable-next-line:naming-convention
public get OO(): PortModel {
return this.getPortOut()!;
}

public getPortIn(): PortModel | null {
return this.scanChildNode(GenericPortModel, (p) => p.isDirectionIn()) || null;
}
Expand Down
79 changes: 21 additions & 58 deletions src/slang/core/abstract/port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {BlackBox} from "./blackbox";
import {SlangNode} from "./nodes";
import {PortOwner} from "./port-owner";
import {StreamPort} from "./stream";
import {canConnectTo} from "./utils/connection-check";
import {canConnectTo, typesCompatibleTo} from "./utils/connection-check";
import {Connections} from "./utils/connections";
import {SlangSubject} from "./utils/events";
import {GenericSpecifications} from "./utils/generics";
Expand Down Expand Up @@ -101,6 +101,18 @@ export abstract class GenericPortModel<O extends PortOwner> extends SlangNode {
this.streamPort.initialize();
}

public get sub(): GenericPortModel<O> {
return this.getStreamSub();
}

public get mapSubs(): Array<GenericPortModel<O>> {
return Array.from(this.getMapSubs());
}

public map(name: string): GenericPortModel<O> {
return this.findMapSub(name);
}

public reconstruct(type: SlangType, portCtor: new (p: GenericPortModel<O> | O, args: PortModelArgs) => PortModel, direction: PortDirection, generic: boolean = false): void {
/*
* When generic == true then this method is called because this port is generic-like and its specification has changed
Expand Down Expand Up @@ -293,52 +305,6 @@ export abstract class GenericPortModel<O extends PortOwner> extends SlangNode {
return type;
}

public anySubStreamConnected(): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kairichard did you remove them because they were not used anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@td5r indeed.

if (this.connectedWith.length !== 0) {
return true;
}

if (this.typeIdentifier === TypeIdentifier.Map) {
for (const sub of this.getMapSubs()) {
if (sub.anySubStreamConnected()) {
return true;
}
}
} else if (this.typeIdentifier === TypeIdentifier.Stream) {
return this.getStreamSub().anySubStreamConnected();
}

return false;
}

public getConnectedType(): SlangType {
const type = new SlangType(null, this.typeIdentifier, true);
switch (this.typeIdentifier) {
case TypeIdentifier.Map:
for (const subPort of this.getMapSubs()) {
if (!subPort.anySubStreamConnected()) {
continue;
}
const subType = subPort.getConnectedType();
if (!subType.isVoid()) {
type.addMapSub(subPort.getName(), subType);
}
}
break;
case TypeIdentifier.Stream:
type.setStreamSub(this.getStreamSub().getConnectedType());
break;
case TypeIdentifier.Generic:
type.setGenericIdentifier(this.getGenericIdentifier());
break;
default:
if (!this.anySubStreamConnected()) {
return new SlangType(null, TypeIdentifier.Unspecified, true);
}
}
return type;
}

public getTypeIdentifier(): TypeIdentifier {
return this.typeIdentifier;
}
Expand Down Expand Up @@ -598,16 +564,6 @@ export abstract class GenericPortModel<O extends PortOwner> extends SlangNode {
fetchedGenerics.specifications.unregisterPort(fetchedGenerics.identifier, this);
});

this.subscribeDisconnected(() => {
if (this.connectedWith.length !== 0) {
return;
}
const newType = specifications.getUnifiedType(identifier);
if (newType && !specifications.get(identifier).equals(newType)) {
specifications.specify(identifier, newType);
}
});

if (!this.isGenericLike()) {
return;
}
Expand All @@ -620,6 +576,13 @@ export abstract class GenericPortModel<O extends PortOwner> extends SlangNode {
this.typeIdentifier = TypeIdentifier.Unspecified;
this.genericIdentifier = undefined;
}
for (const connection of this.getConnectionsTo()) {
const dest = connection.destination;
const src = connection.source;
if (!typesCompatibleTo(src.getType(), dest.getType())) {
src.disconnectTo(dest);
}
}
});
}

Expand Down Expand Up @@ -692,7 +655,7 @@ export abstract class GenericPortModel<O extends PortOwner> extends SlangNode {
*/
private connectTo(destination: PortModel, createGenerics: boolean) {
if (!canConnectTo(this, destination, createGenerics)) {
throw new Error(`cannot connect: ${this.getIdentity()} --> ${destination.getIdentity()}`);
throw new Error(`cannot connect: ${this.getOwnerName()}:${this.getPortReference()} --> ${destination.getOwnerName()}:${destination.getPortReference()}`);
}
if ((createGenerics && this.isGenericLike()) || destination.isTrigger()) {
this.connectDirectlyTo(destination, createGenerics);
Expand Down
6 changes: 5 additions & 1 deletion src/slang/core/abstract/utils/connection-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ function typesMapCompatibleTo(mapTypeA: SlangType, mapTypeB: SlangType): boolean
return true;
}

function typesCompatibleTo(sourceType: SlangType, destinationType: SlangType): boolean {
export function typesCompatibleTo(sourceType: SlangType, destinationType: SlangType): boolean {
// Triggers can always be destinations, even for specifications, maps and streams
if (destinationType.getTypeIdentifier() === TypeIdentifier.Trigger) {
return true;
}

if (sourceType.getTypeIdentifier() === TypeIdentifier.Trigger) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kairichard you may not connect a trigger source port to any other port but the other way around is ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@td5r But that is the case when I start dragging a connection from an operator inport to another outport. What you see in the last gif wouldn't work w/o this change.

return true;
}

// Careful: destinationType.getTypeIdentifier() === TypeIdentifier.Primitive is not identical with destinationType.isPrimitive()
// isPrimitive() is true for Strings, Numbers, etc.
if (destinationType.getTypeIdentifier() === TypeIdentifier.Primitive && sourceType.isPrimitive()) {
Expand Down
12 changes: 0 additions & 12 deletions src/slang/core/abstract/utils/generics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,6 @@ export class GenericSpecifications {
}
}

public getUnifiedType(identifier: string): SlangType | null {
const portSet = this.ports.get(identifier);
if (!portSet) {
return null;
}
let unifiedType = this.get(identifier).getOnlyFixedSubs();
for (const registeredPort of portSet) {
unifiedType = unifiedType.union(registeredPort.getConnectedType());
}
return unifiedType;
}

public subscribeGenericTypeChanged(identifier: string, cb: (type: SlangType | null) => void): Subscription {
return this.getSubject(identifier).subscribe(cb);
}
Expand Down
5 changes: 5 additions & 0 deletions src/slang/core/models/blueprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ export class BlueprintModel extends BlackBox implements HasMoveablePortGroups {
return this.createChildNode(BlueprintPortModel, args);
}

public definePort(args: PortModelArgs): this {
this.createPort(args);
return this;
}

public getShortName(): string {
return this.name;
}
Expand Down
80 changes: 21 additions & 59 deletions src/slang/definitions/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,18 @@ export class SlangType {
return SlangType.new(TypeIdentifier.Unspecified);
}

public static newTrigger(): SlangType {
return SlangType.new(TypeIdentifier.Trigger);
}

public static newString(): SlangType {
return SlangType.new(TypeIdentifier.String);
}

public static newNumber(): SlangType {
return SlangType.new(TypeIdentifier.Number);
}

public static newBoolean(): SlangType {
return SlangType.new(TypeIdentifier.Boolean);
}
Expand All @@ -151,16 +159,22 @@ export class SlangType {
return SlangType.new(TypeIdentifier.Generic).setGenericIdentifier(identifier);
}

public static newMap(): SlangType {
return SlangType.new(TypeIdentifier.Map);
public static newMap(mapEntries?: {[n: string]: SlangType}): SlangType {
const map = SlangType.new(TypeIdentifier.Map);
if (mapEntries) {
Object.entries(mapEntries).forEach(([name, subType]) => {
map.addMapSub(name, subType);
});
}
return map;
}

public static newStream(subTid?: TypeIdentifier): SlangType {
const strType = SlangType.new(TypeIdentifier.Stream);
if (!subTid) {
return strType;
public static newStream(sub?: TypeIdentifier|SlangType): SlangType {
const stream = SlangType.new(TypeIdentifier.Stream);
if (sub) {
stream.setStreamSub(sub instanceof SlangType ? sub : SlangType.new(sub));
}
return SlangType.new(TypeIdentifier.Stream).setStreamSub(SlangType.new(subTid));
return stream;
}

private readonly mapSubs: Map<string, SlangType> | undefined;
Expand Down Expand Up @@ -385,58 +399,6 @@ export class SlangType {
return this.typeIdentifier;
}

/**
* Returns <code>true</code> iff this type contains at least one element that is fixed (i.e. that has not been
* inferred).
*/
public hasAnyFixedSub(): boolean {
if (this.typeIdentifier === TypeIdentifier.Map) {
for (const sub of this.getMapSubs()) {
if (sub[1].hasAnyFixedSub()) {
return true;
}
}
} else if (this.typeIdentifier === TypeIdentifier.Stream) {
return this.getStreamSub().hasAnyFixedSub();
} else if (!this.inferred) {
return true;
}

return false;
}

/**
* Returns a type which is part of this type but contains only fixed elements (i.e. elements that have not been
* inferred).
*/
public getOnlyFixedSubs(): SlangType {
const type = new SlangType(null, this.typeIdentifier);
switch (this.typeIdentifier) {
case TypeIdentifier.Map:
for (const sub of this.getMapSubs()) {
if (!sub[1].hasAnyFixedSub()) {
continue;
}
const subType = sub[1].getOnlyFixedSubs();
if (!subType.isVoid()) {
type.addMapSub(sub[0], subType);
}
}
break;
case TypeIdentifier.Stream:
type.setStreamSub(this.getStreamSub().getOnlyFixedSubs());
break;
case TypeIdentifier.Generic:
type.setGenericIdentifier(this.getGenericIdentifier());
break;
default:
if (!this.hasAnyFixedSub()) {
return SlangType.newUnspecified();
}
}
return type;
}

public isElementaryPort(): boolean {
return this.isPrimitive() || this.isTrigger() || this.isGeneric();
}
Expand Down
4 changes: 1 addition & 3 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
// @ts-ignore
import styling from "./index.scss";
export const STYLING = styling.toString();
export const STYLING: string = `@import url(https://fonts.googleapis.com/css?family=Roboto+Slab:300%7CRoboto);.joint-viewport{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.joint-paper-background,.joint-paper-grid,.joint-paper>svg{position:absolute;top:0;left:0;right:0;bottom:0}[magnet=true]:not(.joint-element){cursor:crosshair}[magnet=true]:not(.joint-element):hover{opacity:.7}.joint-element{cursor:move}.joint-element *{user-drag:none}.joint-element .scalable *{vector-effect:non-scaling-stroke}.marker-source,.marker-target{vector-effect:non-scaling-stroke}.joint-paper{position:relative}.joint-highlight-opacity{opacity:.3}.joint-link .connection,.joint-link .connection-wrap{fill:none}.marker-vertices{opacity:0;cursor:move}.marker-arrowheads{opacity:0;cursor:move;cursor:-webkit-grab;cursor:-moz-grab}.link-tools{opacity:0;cursor:pointer}.link-tools .tool-options{display:none}.joint-link:hover .link-tools,.joint-link:hover .marker-arrowheads,.joint-link:hover .marker-vertices{opacity:1}.marker-vertex-remove{cursor:pointer;opacity:.1}.marker-vertex-group:hover .marker-vertex-remove{opacity:1}.marker-vertex-remove-area{opacity:.1;cursor:pointer}.marker-vertex-group:hover .marker-vertex-remove-area{opacity:1}.joint-element .fobj{overflow:hidden}.joint-element .fobj body{background-color:transparent;margin:0;position:static}.joint-element .fobj div{text-align:center;vertical-align:middle;display:table-cell;padding:0 5px 0 5px}.sl-port{-webkit-transition-duration:.2s;transition-duration:.2s}.sl-view{width:100%;height:100%}.sl-blackbox .sl-rectangle,.sl-blackbox-ghost .sl-rectangle{fill:#fefefe;stroke:#212124;stroke-width:1.5px;paint-order:stroke}.sl-blackbox .sl-label,.sl-blackbox-ghost .sl-label{fill:#212124}.sl-blackbox-ghost .sl-rectangle{fill-opacity:.1}.sl-outer .sl-rectangle{stroke:#212124;fill:#fefefe;stroke-width:1.5px}.sl-outer.sl-blupr-elem .sl-rectangle{cursor:not-allowed;fill:rgba(33,33,36,.1)}.sl-blueprint-port .sl-label{fill:#212124}.sl-port{stroke-width:0;stroke-opacity:.6;-webkit-transition-property:stroke-width;transition-property:stroke-width}.sl-port.sl-stripe{stroke:#fff;fill:#fff}.sl-port.sl-type-number{stroke:#2e49b3;fill:#2e49b3}.sl-port.sl-type-string{stroke:#952e2e;fill:#952e2e}.sl-port.sl-type-boolean{stroke:#ff764d;fill:#ff764d}.sl-port.sl-type-binary{stroke:#83a91d;fill:#83a91d}.sl-port.sl-type-primitive{stroke:#209cee;fill:#209cee}.sl-port.sl-type-trigger{stroke:rgba(152,152,151,.5);fill:rgba(152,152,151,.5)}.sl-port.sl-type-generic{stroke:#b86bff;fill:#b86bff}.sl-port.sl-type-ghost{stroke:rgba(184,107,255,.5);fill:rgba(184,107,255,.5)}.sl-connection-wrap{fill:none;stroke:#989897;stroke-width:10px;stroke-opacity:0;stroke-linecap:round}.sl-connection-wrap:hover{stroke-opacity:.3}.sl-connection-wrap.sl-is-selected{stroke:#212124;stroke-opacity:.2}.sl-connection-wrap.sl-is-selected~.link-tools{display:initial}.sl-connection{vector-effect:none;fill:none;stroke-opacity:1}.sl-connection.sl-type-number{stroke:#2e49b3}.sl-connection.sl-type-string{stroke:#952e2e}.sl-connection.sl-type-boolean{stroke:#ff764d}.sl-connection.sl-type-binary{stroke:#83a91d}.sl-connection.sl-type-primitive{stroke:#209cee}.sl-connection.sl-type-trigger{stroke:rgba(152,152,151,.5)}.sl-connection.sl-type-generic{stroke:#b86bff}.sl-connection.sl-type-ghost{stroke:rgba(184,107,255,.5);stroke-opacity:.3}@-webkit-keyframes port-pulse{from{stroke-width:0}to{stroke-width:8px}}@keyframes port-pulse{from{stroke-width:0}to{stroke-width:8px}}.joint-paper{font-family:Roboto,sans-serif;font-size:16px;font-size:1rem}.joint-highlight-stroke{stroke:none}.available-magnet path{-webkit-animation-name:port-pulse;animation-name:port-pulse;-webkit-animation-duration:.5s;animation-duration:.5s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-direction:alternate;animation-direction:alternate;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}.tool-remove circle{fill:#f74125}.tool-remove path{fill:#fefefe}.sl-port-info{background:#f5f5f5;padding:1px;margin:0;border-radius:8px;font-size:12px;font-size:.75rem}.sl-port-info .sl-port-name{text-decoration:underline;padding:0 6px}.sl-port-info .sl-port-type{color:#fafafa;padding:0 6px;border-radius:15px}.sl-port-info .sl-port-type.sl-type-number{background-color:#2e49b3}.sl-port-info .sl-port-type.sl-type-string{background-color:#952e2e}.sl-port-info .sl-port-type.sl-type-boolean{background-color:#ff764d}.sl-port-info .sl-port-type.sl-type-binary{background-color:#83a91d}.sl-port-info .sl-port-type.sl-type-primitive{background-color:#209cee}.sl-port-info .sl-port-type.sl-type-trigger{background-color:rgba(152,152,151,.5)}.sl-port-info .sl-port-type.sl-type-generic{background-color:#b86bff}.sl-port-info .sl-port-type.sl-type-ghost{background-color:rgba(184,107,255,.5)}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@td5r must be reverted ⚠️

Loading