mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 18:15:21 +03:00
Enable caching in visualization functions (#3618)
PR allows to attach metod pointers as a visualization expressions. This way it allows to attach a runtime instrument that enables caching of intermediate expressions.
# Important Notes
ℹ️ API is backward compatible.
To attach the visualization with caching support, the same `executionContext/attachVisualisation` method is used, but `VisualisationConfig` message should contain the message pointer.
While `VisualisationConfiguration` message has changed, the language server accepts both new and old formats to keep visualisations working in IDE.
#### Old format
```json
{
"executionContextId": "UUID",
"visualisationModule": "local.Unnamed.Main",
"expression": "x -> x.to_text"
}
```
#### New format
```json
{
"executionContextId": "UUID",
"expression": {
"module": "local.Unnamed.Main",
"definedOnType": "local.Unnamed.Main",
"name": "encode"
}
}
```
This commit is contained in:
parent
bd3b778721
commit
98d30bccf3
@ -302,6 +302,7 @@
|
||||
- [Explicit `self`][3569]
|
||||
- [Added benchmarking tool for the language server][3578]
|
||||
- [Support module imports using a qualified name][3608]
|
||||
- [Enable caching in visualisation functions][3618]
|
||||
- [Update Scala compiler and libraries][3631]
|
||||
- [Support importing module methods][3633]
|
||||
|
||||
@ -338,7 +339,8 @@
|
||||
[3562]: https://github.com/enso-org/enso/pull/3562
|
||||
[3538]: https://github.com/enso-org/enso/pull/3538
|
||||
[3538]: https://github.com/enso-org/enso/pull/3569
|
||||
[3578]: https://github.com/enso-org/enso/pull/3578
|
||||
[3618]: https://github.com/enso-org/enso/pull/3618
|
||||
[3608]: https://github.com/enso-org/enso/pull/3608
|
||||
[3608]: https://github.com/enso-org/enso/pull/3608
|
||||
[3631]: https://github.com/enso-org/enso/pull/3631
|
||||
[3633]: https://github.com/enso-org/enso/pull/3633
|
||||
|
@ -27,6 +27,7 @@ transport formats, please look [here](./protocol-architecture).
|
||||
- [`ExpressionUpdate`](#expressionupdate)
|
||||
- [`ExpressionUpdatePayload`](#expressionupdatepayload)
|
||||
- [`VisualisationConfiguration`](#visualisationconfiguration)
|
||||
- [`VisualisationExpression`](#visualisationexpression)
|
||||
- [`SuggestionEntryArgument`](#suggestionentryargument)
|
||||
- [`SuggestionEntry`](#suggestionentry)
|
||||
- [`SuggestionEntryType`](#suggestionentrytype)
|
||||
@ -378,19 +379,17 @@ A configuration object for properties of the visualisation.
|
||||
|
||||
```typescript
|
||||
interface VisualisationConfiguration {
|
||||
/**
|
||||
* An execution context of the visualisation.
|
||||
*/
|
||||
/** An execution context of the visualisation. */
|
||||
executionContextId: UUID;
|
||||
|
||||
/**
|
||||
* A qualified name of the module containing the expression which creates
|
||||
* visualisation.
|
||||
*/
|
||||
visualisationModule: String;
|
||||
/**
|
||||
* The expression that creates a visualisation.
|
||||
*/
|
||||
expression: String;
|
||||
visualisationModule?: String;
|
||||
|
||||
/** An expression that creates a visualisation. */
|
||||
expression: String | MethodPointer;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -243,7 +243,7 @@ final class ContextRegistry(
|
||||
Api.AttachVisualisation(
|
||||
visualisationId,
|
||||
expressionId,
|
||||
convertVisualisationConfig(cfg)
|
||||
cfg.toApi
|
||||
)
|
||||
)
|
||||
} else {
|
||||
@ -263,7 +263,7 @@ final class ContextRegistry(
|
||||
Api.AttachVisualisation(
|
||||
visualisationId,
|
||||
expressionId,
|
||||
convertVisualisationConfig(cfg)
|
||||
cfg.toApi
|
||||
)
|
||||
)
|
||||
} else {
|
||||
@ -301,25 +301,14 @@ final class ContextRegistry(
|
||||
)
|
||||
)
|
||||
|
||||
val configuration = convertVisualisationConfig(cfg)
|
||||
|
||||
handler.forward(
|
||||
Api.ModifyVisualisation(visualisationId, configuration)
|
||||
Api.ModifyVisualisation(visualisationId, cfg.toApi)
|
||||
)
|
||||
} else {
|
||||
sender() ! AccessDenied
|
||||
}
|
||||
}
|
||||
|
||||
private def convertVisualisationConfig(
|
||||
config: VisualisationConfiguration
|
||||
): Api.VisualisationConfiguration =
|
||||
Api.VisualisationConfiguration(
|
||||
executionContextId = config.executionContextId,
|
||||
visualisationModule = config.visualisationModule,
|
||||
expression = config.expression
|
||||
)
|
||||
|
||||
private def getRuntimeStackItem(
|
||||
stackItem: StackItem
|
||||
): Api.StackItem =
|
||||
|
@ -1,9 +1,16 @@
|
||||
package org.enso.languageserver.runtime
|
||||
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
|
||||
/** An object pointing to a method definition.
|
||||
*
|
||||
* @param module the module of the method file
|
||||
* @param definedOnType method type
|
||||
* @param name method name
|
||||
*/
|
||||
case class MethodPointer(module: String, definedOnType: String, name: String)
|
||||
case class MethodPointer(module: String, definedOnType: String, name: String) {
|
||||
|
||||
/** Convert to corresponding [[Api]] message. */
|
||||
def toApi: Api.MethodPointer =
|
||||
Api.MethodPointer(module, definedOnType, name)
|
||||
}
|
||||
|
@ -1,27 +1,201 @@
|
||||
package org.enso.languageserver.runtime
|
||||
|
||||
import java.util.UUID
|
||||
import io.circe.generic.auto._
|
||||
import io.circe.syntax._
|
||||
import io.circe.{Decoder, Encoder, Json}
|
||||
import org.enso.logger.masking.ToLogString
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
|
||||
import org.enso.logger.masking.{MaskedString, ToLogString}
|
||||
import java.util.UUID
|
||||
|
||||
/** A configuration object for properties of the visualisation.
|
||||
*
|
||||
* @param executionContextId an execution context of the visualisation
|
||||
* @param visualisationModule a qualified name of the module containing
|
||||
* the expression which creates visualisation
|
||||
* @param expression the expression that creates a visualisation
|
||||
* @param expression an expression that creates a visualisation
|
||||
*/
|
||||
case class VisualisationConfiguration(
|
||||
executionContextId: UUID,
|
||||
visualisationModule: String,
|
||||
expression: String
|
||||
expression: VisualisationExpression
|
||||
) extends ToLogString {
|
||||
|
||||
/** A qualified module name containing the expression. */
|
||||
def visualisationModule: String =
|
||||
expression.module
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toLogString(shouldMask: Boolean): String =
|
||||
"VisualisationConfiguration(" +
|
||||
s"VisualisationConfiguration(" +
|
||||
s"executionContextId=$executionContextId," +
|
||||
s"visualisationModule=$visualisationModule,expression=" +
|
||||
MaskedString(expression).toLogString(shouldMask) +
|
||||
s"expression=${expression.toLogString(shouldMask)})"
|
||||
|
||||
/** Convert to corresponding [[Api]] message. */
|
||||
def toApi: Api.VisualisationConfiguration =
|
||||
Api.VisualisationConfiguration(
|
||||
executionContextId = executionContextId,
|
||||
expression = expression.toApi
|
||||
)
|
||||
|
||||
}
|
||||
object VisualisationConfiguration {
|
||||
|
||||
/** Create a visualisation configuration.
|
||||
*
|
||||
* @param contextId an execution context of the visualisation
|
||||
* @param module a qualified module name containing the visualisation
|
||||
* @param expression a visualisation expression
|
||||
* @return an instance of [[VisualisationConfiguration]]
|
||||
*/
|
||||
def apply(
|
||||
contextId: UUID,
|
||||
module: String,
|
||||
expression: String
|
||||
): VisualisationConfiguration =
|
||||
new VisualisationConfiguration(
|
||||
contextId,
|
||||
VisualisationExpression.Text(module, expression)
|
||||
)
|
||||
|
||||
/** Create a visualisation configuration.
|
||||
*
|
||||
* @param contextId an execution context of the visualisation
|
||||
* @param expression a visualisation expression
|
||||
* @return an instance of [[VisualisationConfiguration]]
|
||||
*/
|
||||
def apply(
|
||||
contextId: UUID,
|
||||
expression: MethodPointer
|
||||
): VisualisationConfiguration =
|
||||
new VisualisationConfiguration(
|
||||
contextId,
|
||||
VisualisationExpression.ModuleMethod(expression)
|
||||
)
|
||||
|
||||
private object CodecField {
|
||||
|
||||
val Expression = "expression"
|
||||
|
||||
val ExecutionContextId = "executionContextId"
|
||||
|
||||
val VisualisationModule = "visualisationModule"
|
||||
}
|
||||
|
||||
/** Json decoder that supports both old and new formats. */
|
||||
implicit val decoder: Decoder[VisualisationConfiguration] =
|
||||
Decoder.instance { cursor =>
|
||||
cursor.downField(CodecField.Expression).as[String] match {
|
||||
case Left(_) =>
|
||||
for {
|
||||
contextId <- cursor
|
||||
.downField(CodecField.ExecutionContextId)
|
||||
.as[UUID]
|
||||
expression <- cursor
|
||||
.downField(CodecField.Expression)
|
||||
.as[MethodPointer]
|
||||
} yield VisualisationConfiguration(contextId, expression)
|
||||
|
||||
case Right(expression) =>
|
||||
for {
|
||||
contextId <- cursor
|
||||
.downField(CodecField.ExecutionContextId)
|
||||
.as[UUID]
|
||||
visualisationModule <- cursor
|
||||
.downField(CodecField.VisualisationModule)
|
||||
.as[String]
|
||||
} yield VisualisationConfiguration(
|
||||
contextId,
|
||||
visualisationModule,
|
||||
expression
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** A visualisation expression. */
|
||||
sealed trait VisualisationExpression extends ToLogString {
|
||||
|
||||
/** A qualified module name. */
|
||||
def module: String
|
||||
|
||||
/** Convert to corresponding [[Api]] message. */
|
||||
def toApi: Api.VisualisationExpression
|
||||
}
|
||||
object VisualisationExpression {
|
||||
|
||||
/** Visualization expression represented as a text.
|
||||
*
|
||||
* @param module a qualified module name containing the expression
|
||||
* @param expression an expression that creates a visualization
|
||||
*/
|
||||
case class Text(module: String, expression: String)
|
||||
extends VisualisationExpression {
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toApi: Api.VisualisationExpression =
|
||||
Api.VisualisationExpression.Text(module, expression)
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toLogString(shouldMask: Boolean): String =
|
||||
s"Text(module=$module" +
|
||||
s",expression=" +
|
||||
(if (shouldMask) STUB else expression) +
|
||||
")"
|
||||
}
|
||||
|
||||
/** Visualization expression represented as a module method.
|
||||
*
|
||||
* @param methodPointer a pointer to a method definition
|
||||
*/
|
||||
case class ModuleMethod(methodPointer: MethodPointer)
|
||||
extends VisualisationExpression {
|
||||
|
||||
/** @inheritdoc */
|
||||
override val module: String = methodPointer.module
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toApi: Api.VisualisationExpression =
|
||||
Api.VisualisationExpression.ModuleMethod(methodPointer.toApi)
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toLogString(shouldMask: Boolean): String =
|
||||
s"ModuleMethod(methodPointer=$methodPointer)"
|
||||
}
|
||||
|
||||
private object CodecField {
|
||||
|
||||
val Type = "type"
|
||||
}
|
||||
|
||||
private object PayloadType {
|
||||
|
||||
val Text = "Text"
|
||||
|
||||
val ModuleMethod = "ModuleMethod"
|
||||
}
|
||||
|
||||
implicit val encoder: Encoder[VisualisationExpression] =
|
||||
Encoder.instance[VisualisationExpression] {
|
||||
case text: VisualisationExpression.Text =>
|
||||
Encoder[VisualisationExpression.Text]
|
||||
.apply(text)
|
||||
.deepMerge(Json.obj(CodecField.Type -> PayloadType.Text.asJson))
|
||||
|
||||
case moduleMethod: VisualisationExpression.ModuleMethod =>
|
||||
Encoder[VisualisationExpression.ModuleMethod]
|
||||
.apply(moduleMethod)
|
||||
.deepMerge(
|
||||
Json.obj(CodecField.Type -> PayloadType.ModuleMethod.asJson)
|
||||
)
|
||||
}
|
||||
|
||||
implicit val decoder: Decoder[VisualisationExpression] =
|
||||
Decoder.instance { cursor =>
|
||||
cursor.downField(CodecField.Type).as[String].flatMap {
|
||||
case PayloadType.Text =>
|
||||
Decoder[VisualisationExpression.Text].tryDecode(cursor)
|
||||
|
||||
case PayloadType.ModuleMethod =>
|
||||
Decoder[VisualisationExpression.ModuleMethod].tryDecode(cursor)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,10 @@ package org.enso.languageserver.websocket.json
|
||||
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
import io.circe.literal._
|
||||
import org.enso.languageserver.runtime.VisualisationConfiguration
|
||||
import org.enso.languageserver.runtime.{
|
||||
VisualisationConfiguration,
|
||||
VisualisationExpression
|
||||
}
|
||||
|
||||
object ExecutionContextJsonMessages {
|
||||
|
||||
@ -110,6 +113,8 @@ object ExecutionContextJsonMessages {
|
||||
expressionId: Api.ExpressionId,
|
||||
configuration: VisualisationConfiguration
|
||||
) =
|
||||
configuration.expression match {
|
||||
case VisualisationExpression.Text(module, expression) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/executeExpression",
|
||||
@ -119,19 +124,41 @@ object ExecutionContextJsonMessages {
|
||||
"expressionId": $expressionId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": ${configuration.executionContextId},
|
||||
"visualisationModule": ${configuration.visualisationModule},
|
||||
"expression": ${configuration.expression}
|
||||
"visualisationModule": $module,
|
||||
"expression": $expression
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
case VisualisationExpression.ModuleMethod(methodPointer) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/executeExpression",
|
||||
"id": $reqId,
|
||||
"params": {
|
||||
"visualisationId": $visualisationId,
|
||||
"expressionId": $expressionId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": ${configuration.executionContextId},
|
||||
"expression": {
|
||||
"module": ${methodPointer.module},
|
||||
"definedOnType": ${methodPointer.definedOnType},
|
||||
"name": ${methodPointer.name}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
|
||||
def executionContextAttachVisualisationRequest(
|
||||
reqId: Int,
|
||||
visualisationId: Api.VisualisationId,
|
||||
expressionId: Api.ExpressionId,
|
||||
configuration: VisualisationConfiguration
|
||||
) =
|
||||
) = {
|
||||
configuration.expression match {
|
||||
case VisualisationExpression.Text(module, expression) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/attachVisualisation",
|
||||
@ -141,12 +168,33 @@ object ExecutionContextJsonMessages {
|
||||
"expressionId": $expressionId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": ${configuration.executionContextId},
|
||||
"visualisationModule": ${configuration.visualisationModule},
|
||||
"expression": ${configuration.expression}
|
||||
"visualisationModule": $module,
|
||||
"expression": $expression
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
case VisualisationExpression.ModuleMethod(methodPointer) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/attachVisualisation",
|
||||
"id": $reqId,
|
||||
"params": {
|
||||
"visualisationId": $visualisationId,
|
||||
"expressionId": $expressionId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": ${configuration.executionContextId},
|
||||
"expression": {
|
||||
"module": ${methodPointer.module},
|
||||
"definedOnType": ${methodPointer.definedOnType},
|
||||
"name": ${methodPointer.name}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
def executionContextModuleNotFound(
|
||||
reqId: Int,
|
||||
@ -215,7 +263,9 @@ object ExecutionContextJsonMessages {
|
||||
reqId: Int,
|
||||
visualisationId: Api.VisualisationId,
|
||||
configuration: VisualisationConfiguration
|
||||
) =
|
||||
) = {
|
||||
configuration.expression match {
|
||||
case VisualisationExpression.Text(module, expression) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/modifyVisualisation",
|
||||
@ -224,12 +274,32 @@ object ExecutionContextJsonMessages {
|
||||
"visualisationId": $visualisationId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": ${configuration.executionContextId},
|
||||
"visualisationModule": ${configuration.visualisationModule},
|
||||
"expression": ${configuration.expression}
|
||||
"visualisationModule": $module,
|
||||
"expression": $expression
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
case VisualisationExpression.ModuleMethod(methodPointer) =>
|
||||
json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/modifyVisualisation",
|
||||
"id": $reqId,
|
||||
"params": {
|
||||
"visualisationId": $visualisationId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": ${configuration.executionContextId},
|
||||
"expression": {
|
||||
"module": ${methodPointer.module},
|
||||
"definedOnType": ${methodPointer.definedOnType},
|
||||
"name": ${methodPointer.name}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
def executionContextGetComponentGroupsRequest(
|
||||
reqId: Int,
|
||||
|
@ -1,8 +1,10 @@
|
||||
package org.enso.languageserver.websocket.json
|
||||
import java.util.UUID
|
||||
|
||||
import io.circe.literal._
|
||||
import org.enso.languageserver.runtime.VisualisationConfiguration
|
||||
import org.enso.languageserver.runtime.{
|
||||
MethodPointer,
|
||||
VisualisationConfiguration
|
||||
}
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
import org.enso.text.editing.model
|
||||
|
||||
@ -37,7 +39,60 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
config
|
||||
)
|
||||
) =>
|
||||
config.expression shouldBe visualisationConfig.expression
|
||||
config.expression shouldBe visualisationConfig.expression.toApi
|
||||
config.visualisationModule shouldBe visualisationConfig.visualisationModule
|
||||
config.executionContextId shouldBe visualisationConfig.executionContextId
|
||||
requestId
|
||||
|
||||
case msg =>
|
||||
fail(s"Unexpected message: $msg")
|
||||
}
|
||||
|
||||
runtimeConnectorProbe.lastSender ! Api.Response(
|
||||
requestId,
|
||||
Api.VisualisationAttached()
|
||||
)
|
||||
client.expectJson(ExecutionContextJsonMessages.ok(1))
|
||||
}
|
||||
|
||||
"allow attaching method pointer as a visualisation expression" in {
|
||||
val visualisationId = UUID.randomUUID()
|
||||
val expressionId = UUID.randomUUID()
|
||||
|
||||
val client = getInitialisedWsClient()
|
||||
val contextId = createExecutionContext(client)
|
||||
val visualisationModule = "Foo.Bar"
|
||||
val visualisationMethod = "baz"
|
||||
val visualisationConfig =
|
||||
VisualisationConfiguration(
|
||||
contextId,
|
||||
MethodPointer(
|
||||
visualisationModule,
|
||||
visualisationModule,
|
||||
visualisationMethod
|
||||
)
|
||||
)
|
||||
|
||||
client.send(
|
||||
ExecutionContextJsonMessages.executionContextAttachVisualisationRequest(
|
||||
1,
|
||||
visualisationId,
|
||||
expressionId,
|
||||
visualisationConfig
|
||||
)
|
||||
)
|
||||
|
||||
val requestId =
|
||||
runtimeConnectorProbe.receiveN(1).head match {
|
||||
case Api.Request(
|
||||
requestId,
|
||||
Api.AttachVisualisation(
|
||||
`visualisationId`,
|
||||
`expressionId`,
|
||||
config
|
||||
)
|
||||
) =>
|
||||
config.expression shouldBe visualisationConfig.expression.toApi
|
||||
config.visualisationModule shouldBe visualisationConfig.visualisationModule
|
||||
config.executionContextId shouldBe visualisationConfig.executionContextId
|
||||
requestId
|
||||
@ -106,7 +161,7 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
config
|
||||
)
|
||||
) =>
|
||||
config.expression shouldBe visualisationConfig.expression
|
||||
config.expression shouldBe visualisationConfig.expression.toApi
|
||||
config.visualisationModule shouldBe visualisationConfig.visualisationModule
|
||||
config.executionContextId shouldBe visualisationConfig.executionContextId
|
||||
requestId
|
||||
@ -155,7 +210,7 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
config
|
||||
)
|
||||
) =>
|
||||
config.expression shouldBe visualisationConfig.expression
|
||||
config.expression shouldBe visualisationConfig.expression.toApi
|
||||
config.visualisationModule shouldBe visualisationConfig.visualisationModule
|
||||
config.executionContextId shouldBe visualisationConfig.executionContextId
|
||||
requestId
|
||||
@ -220,17 +275,14 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
val expressionId = UUID.randomUUID()
|
||||
val client = getInitialisedWsClient()
|
||||
val contextId = createExecutionContext(client)
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/detachVisualisation",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"contextId": $contextId,
|
||||
"visualisationId": $visualisationId,
|
||||
"expressionId": $expressionId
|
||||
}
|
||||
}
|
||||
""")
|
||||
client.send(
|
||||
ExecutionContextJsonMessages.executionContextDetachVisualisationRequest(
|
||||
1,
|
||||
contextId,
|
||||
visualisationId,
|
||||
expressionId
|
||||
)
|
||||
)
|
||||
val requestId =
|
||||
runtimeConnectorProbe.receiveN(1).head match {
|
||||
case Api.Request(
|
||||
@ -259,17 +311,14 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
val expressionId = UUID.randomUUID()
|
||||
val contextId = UUID.randomUUID()
|
||||
val client = getInitialisedWsClient()
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/detachVisualisation",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"contextId": $contextId,
|
||||
"visualisationId": $visualisationId,
|
||||
"expressionId": $expressionId
|
||||
}
|
||||
}
|
||||
""")
|
||||
client.send(
|
||||
ExecutionContextJsonMessages.executionContextDetachVisualisationRequest(
|
||||
1,
|
||||
contextId,
|
||||
visualisationId,
|
||||
expressionId
|
||||
)
|
||||
)
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id" : 1,
|
||||
@ -291,20 +340,13 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
val contextId = createExecutionContext(client)
|
||||
val visualisationConfig =
|
||||
VisualisationConfiguration(contextId, "Foo.Bar.baz", "a=x+y")
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/modifyVisualisation",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"visualisationId": $visualisationId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": $contextId,
|
||||
"visualisationModule": ${visualisationConfig.visualisationModule},
|
||||
"expression": ${visualisationConfig.expression}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
client.send(
|
||||
ExecutionContextJsonMessages.executionContextModifyVisualisationRequest(
|
||||
1,
|
||||
visualisationId,
|
||||
visualisationConfig
|
||||
)
|
||||
)
|
||||
|
||||
val requestId =
|
||||
runtimeConnectorProbe.receiveN(1).head match {
|
||||
@ -312,7 +354,7 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
requestId,
|
||||
Api.ModifyVisualisation(`visualisationId`, config)
|
||||
) =>
|
||||
config.expression shouldBe visualisationConfig.expression
|
||||
config.expression shouldBe visualisationConfig.expression.toApi
|
||||
config.visualisationModule shouldBe visualisationConfig.visualisationModule
|
||||
config.executionContextId shouldBe visualisationConfig.executionContextId
|
||||
requestId
|
||||
@ -334,20 +376,14 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
val client = getInitialisedWsClient()
|
||||
val visualisationConfig =
|
||||
VisualisationConfiguration(contextId, "Foo.Bar.baz", "a=x+y")
|
||||
client.send(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"method": "executionContext/modifyVisualisation",
|
||||
"id": 1,
|
||||
"params": {
|
||||
"visualisationId": $visualisationId,
|
||||
"visualisationConfig": {
|
||||
"executionContextId": $contextId,
|
||||
"visualisationModule": ${visualisationConfig.visualisationModule},
|
||||
"expression": ${visualisationConfig.expression}
|
||||
}
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
client.send(
|
||||
ExecutionContextJsonMessages.executionContextModifyVisualisationRequest(
|
||||
1,
|
||||
visualisationId,
|
||||
visualisationConfig
|
||||
)
|
||||
)
|
||||
client.expectJson(json"""
|
||||
{ "jsonrpc": "2.0",
|
||||
"id" : 1,
|
||||
@ -358,7 +394,6 @@ class VisualisationOperationsTest extends BaseServerTest {
|
||||
}
|
||||
""")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def createExecutionContext(client: WsTestClient): UUID = {
|
||||
|
@ -477,26 +477,76 @@ object Runtime {
|
||||
expressionId: ExpressionId
|
||||
)
|
||||
|
||||
/** A visualization expression. */
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
@JsonSubTypes(
|
||||
Array(
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[VisualisationExpression.Text],
|
||||
name = "visualisationExpressionText"
|
||||
),
|
||||
new JsonSubTypes.Type(
|
||||
value = classOf[VisualisationExpression.ModuleMethod],
|
||||
name = "visualisationExpressionModuleMethod"
|
||||
)
|
||||
)
|
||||
)
|
||||
sealed trait VisualisationExpression extends ToLogString {
|
||||
def module: String
|
||||
}
|
||||
object VisualisationExpression {
|
||||
|
||||
/** Visualization expression represented as a text.
|
||||
*
|
||||
* @param module a qualified module name containing the expression
|
||||
* @param expression an expression that creates a visualization
|
||||
*/
|
||||
case class Text(module: String, expression: String)
|
||||
extends VisualisationExpression {
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toLogString(shouldMask: Boolean): String =
|
||||
s"Text(module=$module" +
|
||||
s",expression=" +
|
||||
(if (shouldMask) STUB else expression) +
|
||||
")"
|
||||
}
|
||||
|
||||
/** Visualization expression represented as a module method.
|
||||
*
|
||||
* @param methodPointer a pointer to a method definition
|
||||
*/
|
||||
case class ModuleMethod(methodPointer: MethodPointer)
|
||||
extends VisualisationExpression {
|
||||
|
||||
/** @inheritdoc */
|
||||
override val module: String = methodPointer.module
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toLogString(shouldMask: Boolean): String =
|
||||
s"ModuleMethod(methodPointer=$methodPointer)"
|
||||
}
|
||||
}
|
||||
|
||||
/** A configuration object for properties of the visualisation.
|
||||
*
|
||||
* @param executionContextId an execution context of the visualisation
|
||||
* @param visualisationModule a qualified name of the module containing
|
||||
* the expression which creates visualisation
|
||||
* @param expression the expression that creates a visualisation
|
||||
*/
|
||||
case class VisualisationConfiguration(
|
||||
executionContextId: ContextId,
|
||||
visualisationModule: String,
|
||||
expression: String
|
||||
expression: VisualisationExpression
|
||||
) extends ToLogString {
|
||||
|
||||
/** A qualified module name containing the expression. */
|
||||
def visualisationModule: String =
|
||||
expression.module
|
||||
|
||||
/** @inheritdoc */
|
||||
override def toLogString(shouldMask: Boolean): String =
|
||||
s"VisualisationConfiguration(" +
|
||||
s"executionContextId=$executionContextId," +
|
||||
s"visualisationModule=$visualisationModule,expression=" +
|
||||
(if (shouldMask) STUB else expression) +
|
||||
")"
|
||||
s"expression=${expression.toLogString(shouldMask)})"
|
||||
}
|
||||
|
||||
/** An operation applied to the suggestion argument. */
|
||||
|
@ -330,4 +330,5 @@ public class IdExecutionInstrument extends TruffleInstrument implements IdExecut
|
||||
onExceptionalCallback,
|
||||
timer));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -414,7 +414,60 @@ class RuntimeServerTest
|
||||
)
|
||||
}
|
||||
|
||||
it should "push method with default arguments" in {
|
||||
it should "push method with default arguments on top of the stack" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Enso_Test.Test.Main"
|
||||
|
||||
val metadata = new Metadata
|
||||
val idFoo = metadata.addItem(35, 6)
|
||||
|
||||
val code =
|
||||
"""import Standard.Base.IO
|
||||
|
|
||||
|foo x=0 = x + 42
|
||||
|
|
||||
|main =
|
||||
| IO.println foo
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
val contents = metadata.appendToCode(code)
|
||||
val mainFile = context.writeMain(contents)
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// open file
|
||||
context.send(
|
||||
Api.Request(Api.OpenFileNotification(mainFile, contents))
|
||||
)
|
||||
context.receiveNone shouldEqual None
|
||||
|
||||
// push main
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.PushContextRequest(
|
||||
contextId,
|
||||
Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "foo"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receiveNIgnoreStdLib(3) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
TestMessages.update(contextId, idFoo, ConstantsGen.INTEGER),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
context.consumeOut shouldEqual List()
|
||||
}
|
||||
|
||||
it should "push method with default arguments on the stack" in {
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val moduleName = "Enso_Test.Test.Main"
|
||||
@ -1013,7 +1066,9 @@ class RuntimeServerTest
|
||||
contextId,
|
||||
mainFoo,
|
||||
ConstantsGen.INTEGER,
|
||||
Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "foo")
|
||||
Api
|
||||
.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "foo"),
|
||||
fromCache = true
|
||||
),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
|
@ -23,7 +23,7 @@ import java.util.UUID
|
||||
import scala.io.Source
|
||||
|
||||
@scala.annotation.nowarn("msg=multiarg infix syntax")
|
||||
class RuntimeVisualisationsTest
|
||||
class RuntimeVisualizationsTest
|
||||
extends AnyFlatSpec
|
||||
with Matchers
|
||||
with BeforeAndAfterEach {
|
||||
@ -234,13 +234,43 @@ class RuntimeVisualisationsTest
|
||||
|
||||
object Visualisation {
|
||||
|
||||
val metadata = new Metadata
|
||||
|
||||
val code =
|
||||
metadata.appendToCode(
|
||||
"""
|
||||
|encode = x -> x.to_text
|
||||
|encode x = x.to_text
|
||||
|
|
||||
|incAndEncode = x -> encode x+1
|
||||
|incAndEncode x =
|
||||
| y = x + 1
|
||||
| encode y
|
||||
|
|
||||
|""".stripMargin
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
object AnnotatedVisualisation {
|
||||
|
||||
val metadata = new Metadata
|
||||
val idIncY = metadata.addItem(50, 5)
|
||||
val idIncRes = metadata.addItem(66, 8)
|
||||
val idIncMethod = metadata.addItem(25, 58)
|
||||
|
||||
val code =
|
||||
metadata.appendToCode(
|
||||
"""import Standard.Base.IO
|
||||
|
|
||||
|incAndEncode x =
|
||||
| y = x + 1
|
||||
| res = encode y
|
||||
| res
|
||||
|
|
||||
|encode x =
|
||||
| IO.println "encoding..."
|
||||
| x.to_text
|
||||
|""".stripMargin.linesIterator.mkString("\n")
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@ -311,12 +341,14 @@ class RuntimeVisualisationsTest
|
||||
idMainRes,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"x -> encode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(3)
|
||||
attachVisualisationResponses should contain allOf (
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
@ -423,12 +455,14 @@ class RuntimeVisualisationsTest
|
||||
context.Main.idMainX,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"x -> encode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(2)
|
||||
attachVisualisationResponses should contain(
|
||||
Api.Response(requestId, Api.VisualisationAttached())
|
||||
@ -552,12 +586,14 @@ class RuntimeVisualisationsTest
|
||||
context.Main.idMainX,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"x -> encode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(2)
|
||||
attachVisualisationResponses should contain(
|
||||
Api.Response(requestId, Api.VisualisationAttached())
|
||||
@ -675,12 +711,14 @@ class RuntimeVisualisationsTest
|
||||
context.Main.idMainZ,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"encode"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(2)
|
||||
attachVisualisationResponses should contain(
|
||||
Api.Response(requestId, Api.VisualisationAttached())
|
||||
@ -796,12 +834,14 @@ class RuntimeVisualisationsTest
|
||||
context.Main.idMainX,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"x -> encode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val attachVisualisationResponses = context.receiveN(2)
|
||||
attachVisualisationResponses should contain(
|
||||
@ -832,12 +872,14 @@ class RuntimeVisualisationsTest
|
||||
visualisationId,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"x -> incAndEncode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val modifyVisualisationResponses = context.receiveN(2)
|
||||
modifyVisualisationResponses should contain(
|
||||
Api.Response(requestId, Api.VisualisationModified())
|
||||
@ -899,12 +941,14 @@ class RuntimeVisualisationsTest
|
||||
context.Main.idMainX,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"x -> encode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receiveN(3) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
Api.Response(
|
||||
@ -1052,12 +1096,14 @@ class RuntimeVisualisationsTest
|
||||
context.Main.idMainX,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"encode"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(2)
|
||||
attachVisualisationResponses should contain(
|
||||
Api.Response(requestId, Api.VisualisationAttached())
|
||||
@ -1157,12 +1203,14 @@ class RuntimeVisualisationsTest
|
||||
context.Main.idMainX,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"x -> encode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val attachVisualisationResponses = context.receiveN(2)
|
||||
attachVisualisationResponses should contain(
|
||||
@ -1193,12 +1241,14 @@ class RuntimeVisualisationsTest
|
||||
visualisationId,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"x -> incAndEncode x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
// detach visualisation
|
||||
context.send(
|
||||
Api.Request(
|
||||
@ -1282,12 +1332,14 @@ class RuntimeVisualisationsTest
|
||||
idMain,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Test.Undefined",
|
||||
"x -> x"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receiveN(1) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.ModuleNotFound("Test.Undefined"))
|
||||
)
|
||||
@ -1342,12 +1394,14 @@ class RuntimeVisualisationsTest
|
||||
idMain,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Standard.Visualization.Id",
|
||||
"x -> x.default_visualization.to_text"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val attachVisualisationResponses = context.receiveN(4)
|
||||
attachVisualisationResponses should contain allOf (
|
||||
@ -1429,12 +1483,14 @@ class RuntimeVisualisationsTest
|
||||
idMain,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Main",
|
||||
"Main.does_not_exist"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receiveN(1) should contain theSameElementsAs Seq(
|
||||
Api.Response(
|
||||
requestId,
|
||||
@ -1503,12 +1559,14 @@ class RuntimeVisualisationsTest
|
||||
idMain,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
moduleName,
|
||||
"x -> x.visualise_me"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receiveN(3) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
Api.Response(
|
||||
@ -1608,12 +1666,14 @@ class RuntimeVisualisationsTest
|
||||
idMain,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"inc_and_encode"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receiveN(3) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
Api.Response(
|
||||
@ -1720,12 +1780,14 @@ class RuntimeVisualisationsTest
|
||||
idMain,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
moduleName,
|
||||
"x -> x.catch_primitive _.to_text"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(3)
|
||||
attachVisualisationResponses should contain allOf (
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
@ -1806,12 +1868,14 @@ class RuntimeVisualisationsTest
|
||||
idMain,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
moduleName,
|
||||
"x -> Panic.catch_primitive x caught_panic-> caught_panic.payload.to_text"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
context.receiveN(4) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
TestMessages.panic(
|
||||
@ -1920,12 +1984,14 @@ class RuntimeVisualisationsTest
|
||||
idMain,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.Text(
|
||||
visualisationModule,
|
||||
visualisationCode
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(3)
|
||||
attachVisualisationResponses should contain allOf (
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
@ -1948,4 +2014,284 @@ class RuntimeVisualisationsTest
|
||||
val stringified = new String(data)
|
||||
stringified shouldEqual """{ "kind": "Dataflow", "message": "The List is empty."}"""
|
||||
}
|
||||
|
||||
it should "attach method pointer visualisation" in {
|
||||
val idMainRes = context.Main.metadata.addItem(99, 1)
|
||||
val contents = context.Main.code
|
||||
val mainFile = context.writeMain(context.Main.code)
|
||||
val visualisationFile =
|
||||
context.writeInSrcDir("Visualisation", context.Visualisation.code)
|
||||
|
||||
context.send(
|
||||
Api.Request(
|
||||
Api.OpenFileNotification(
|
||||
visualisationFile,
|
||||
context.Visualisation.code
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val visualisationId = UUID.randomUUID()
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// Open the new file
|
||||
context.send(
|
||||
Api.Request(Api.OpenFileNotification(mainFile, contents))
|
||||
)
|
||||
context.receiveNone shouldEqual None
|
||||
|
||||
// push main
|
||||
val item1 = Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer("Enso_Test.Test.Main", "Enso_Test.Test.Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
context.send(
|
||||
Api.Request(requestId, Api.PushContextRequest(contextId, item1))
|
||||
)
|
||||
context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
context.Main.Update.mainX(contextId),
|
||||
context.Main.Update.mainY(contextId),
|
||||
context.Main.Update.mainZ(contextId),
|
||||
TestMessages.update(contextId, idMainRes, ConstantsGen.INTEGER),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
|
||||
// attach visualisation
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.AttachVisualisation(
|
||||
visualisationId,
|
||||
idMainRes,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.ModuleMethod(
|
||||
Api.MethodPointer(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"incAndEncode"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(3)
|
||||
attachVisualisationResponses should contain allOf (
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
val Some(data) = attachVisualisationResponses.collectFirst {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`idMainRes`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data.sameElements("51".getBytes) shouldBe true
|
||||
|
||||
// recompute
|
||||
context.send(
|
||||
Api.Request(requestId, Api.RecomputeContextRequest(contextId, None))
|
||||
)
|
||||
|
||||
val recomputeResponses = context.receiveN(3)
|
||||
recomputeResponses should contain allOf (
|
||||
Api.Response(requestId, Api.RecomputeContextResponse(contextId)),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
val Some(data2) = recomputeResponses.collectFirst {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`idMainRes`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data2.sameElements("51".getBytes) shouldBe true
|
||||
}
|
||||
|
||||
it should "cache intermediate visualization expressions" in {
|
||||
val idMainRes = context.Main.metadata.addItem(99, 1)
|
||||
val contents = context.Main.code
|
||||
val mainFile = context.writeMain(context.Main.code)
|
||||
val moduleName = "Enso_Test.Test.Main"
|
||||
val visualisationFile =
|
||||
context.writeInSrcDir(
|
||||
"Visualisation",
|
||||
context.AnnotatedVisualisation.code
|
||||
)
|
||||
|
||||
context.send(
|
||||
Api.Request(
|
||||
Api.OpenFileNotification(
|
||||
visualisationFile,
|
||||
context.AnnotatedVisualisation.code
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val contextId = UUID.randomUUID()
|
||||
val requestId = UUID.randomUUID()
|
||||
val visualisationId = UUID.randomUUID()
|
||||
|
||||
// create context
|
||||
context.send(Api.Request(requestId, Api.CreateContextRequest(contextId)))
|
||||
context.receive shouldEqual Some(
|
||||
Api.Response(requestId, Api.CreateContextResponse(contextId))
|
||||
)
|
||||
|
||||
// Open the new file
|
||||
context.send(
|
||||
Api.Request(Api.OpenFileNotification(mainFile, contents))
|
||||
)
|
||||
context.receiveNone shouldEqual None
|
||||
|
||||
// push main
|
||||
val item1 = Api.StackItem.ExplicitCall(
|
||||
Api.MethodPointer(moduleName, "Enso_Test.Test.Main", "main"),
|
||||
None,
|
||||
Vector()
|
||||
)
|
||||
context.send(
|
||||
Api.Request(requestId, Api.PushContextRequest(contextId, item1))
|
||||
)
|
||||
context.receiveNIgnoreStdLib(6) should contain theSameElementsAs Seq(
|
||||
Api.Response(requestId, Api.PushContextResponse(contextId)),
|
||||
context.Main.Update.mainX(contextId),
|
||||
context.Main.Update.mainY(contextId),
|
||||
context.Main.Update.mainZ(contextId),
|
||||
TestMessages.update(contextId, idMainRes, ConstantsGen.INTEGER),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
context.consumeOut shouldEqual List()
|
||||
|
||||
// attach visualisation
|
||||
context.send(
|
||||
Api.Request(
|
||||
requestId,
|
||||
Api.AttachVisualisation(
|
||||
visualisationId,
|
||||
idMainRes,
|
||||
Api.VisualisationConfiguration(
|
||||
contextId,
|
||||
Api.VisualisationExpression.ModuleMethod(
|
||||
Api.MethodPointer(
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"Enso_Test.Test.Visualisation",
|
||||
"incAndEncode"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val attachVisualisationResponses = context.receiveN(3)
|
||||
attachVisualisationResponses should contain allOf (
|
||||
Api.Response(requestId, Api.VisualisationAttached()),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
val Some(data) = attachVisualisationResponses.collectFirst {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`idMainRes`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data.sameElements("51".getBytes) shouldBe true
|
||||
context.consumeOut shouldEqual List("encoding...")
|
||||
|
||||
// recompute
|
||||
context.send(
|
||||
Api.Request(requestId, Api.RecomputeContextRequest(contextId, None))
|
||||
)
|
||||
|
||||
val recomputeResponses = context.receiveN(3)
|
||||
recomputeResponses should contain allOf (
|
||||
Api.Response(requestId, Api.RecomputeContextResponse(contextId)),
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
val Some(data2) = recomputeResponses.collectFirst {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`idMainRes`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data2.sameElements("51".getBytes) shouldBe true
|
||||
context.consumeOut shouldEqual List()
|
||||
|
||||
// Modify the visualization file
|
||||
context.send(
|
||||
Api.Request(
|
||||
Api.EditFileNotification(
|
||||
visualisationFile,
|
||||
Seq(
|
||||
TextEdit(
|
||||
model.Range(model.Position(3, 12), model.Position(3, 13)),
|
||||
"2"
|
||||
)
|
||||
),
|
||||
execute = true
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val editFileResponse = context.receiveN(2)
|
||||
editFileResponse should contain(
|
||||
context.executionComplete(contextId)
|
||||
)
|
||||
val Some(data3) = editFileResponse.collectFirst {
|
||||
case Api.Response(
|
||||
None,
|
||||
Api.VisualisationUpdate(
|
||||
Api.VisualisationContext(
|
||||
`visualisationId`,
|
||||
`contextId`,
|
||||
`idMainRes`
|
||||
),
|
||||
data
|
||||
)
|
||||
) =>
|
||||
data
|
||||
}
|
||||
data3.sameElements("52".getBytes) shouldBe true
|
||||
context.consumeOut shouldEqual List("encoding...")
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import com.oracle.truffle.api.source.SourceSection;
|
||||
import org.enso.interpreter.runtime.callable.function.Function;
|
||||
import org.enso.interpreter.runtime.tag.IdentifiedTag;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -92,8 +93,13 @@ public class FunctionCallInstrumentationNode extends Node implements Instrumenta
|
||||
FunctionCall functionCall,
|
||||
Object[] arguments,
|
||||
@Cached InteropApplicationNode interopApplicationNode) {
|
||||
Object[] callArguments =
|
||||
Arrays.copyOf(
|
||||
functionCall.getArguments(), functionCall.getArguments().length + arguments.length);
|
||||
System.arraycopy(
|
||||
arguments, 0, callArguments, functionCall.getArguments().length, arguments.length);
|
||||
return interopApplicationNode.execute(
|
||||
functionCall.function, functionCall.state, functionCall.arguments);
|
||||
functionCall.function, functionCall.state, callArguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.enso.interpreter.service;
|
||||
|
||||
import com.oracle.truffle.api.CallTarget;
|
||||
import com.oracle.truffle.api.CompilerDirectives;
|
||||
import com.oracle.truffle.api.TruffleLogger;
|
||||
import com.oracle.truffle.api.instrumentation.EventBinding;
|
||||
@ -48,6 +49,8 @@ import java.util.function.Consumer;
|
||||
* language.
|
||||
*/
|
||||
public class ExecutionService {
|
||||
|
||||
private static final String MAIN_METHOD = "main";
|
||||
private final Context context;
|
||||
private final Optional<IdExecutionService> idExecutionInstrument;
|
||||
private final NotificationHandler.Forwarder notificationForwarder;
|
||||
@ -86,7 +89,7 @@ public class ExecutionService {
|
||||
return logger;
|
||||
}
|
||||
|
||||
private FunctionCallInstrumentationNode.FunctionCall prepareFunctionCall(
|
||||
public FunctionCallInstrumentationNode.FunctionCall prepareFunctionCall(
|
||||
Module module, String consName, String methodName)
|
||||
throws ConstructorNotFoundException, MethodNotFoundException {
|
||||
ModuleScope scope = module.compileScope(context);
|
||||
@ -99,8 +102,9 @@ public class ExecutionService {
|
||||
if (function == null) {
|
||||
throw new MethodNotFoundException(module.getName().toString(), atomConstructor, methodName);
|
||||
}
|
||||
return new FunctionCallInstrumentationNode.FunctionCall(
|
||||
function, EmptyMap.create(), new Object[] {});
|
||||
Object[] arguments =
|
||||
MAIN_METHOD.equals(methodName) ? new Object[] {} : new Object[] {atomConstructor};
|
||||
return new FunctionCallInstrumentationNode.FunctionCall(function, EmptyMap.create(), arguments);
|
||||
}
|
||||
|
||||
public void initializeLanguageServerConnection(Endpoint endpoint) {
|
||||
@ -252,6 +256,55 @@ public class ExecutionService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function with the given argument and attaching an execution instrument.
|
||||
*
|
||||
* @param module the module providing scope for the function
|
||||
* @param function the function object
|
||||
* @param argument the argument applied to the function
|
||||
* @param cache the runtime cache
|
||||
* @return the result of calling the function
|
||||
*/
|
||||
public Object callFunctionWithInstrument(
|
||||
Module module, Object function, Object argument, RuntimeCache cache)
|
||||
throws UnsupportedTypeException, ArityException, UnsupportedMessageException {
|
||||
UUID nextExecutionItem = null;
|
||||
CallTarget entryCallTarget =
|
||||
(function instanceof Function) ? ((Function) function).getCallTarget() : null;
|
||||
MethodCallsCache methodCallsCache = new MethodCallsCache();
|
||||
UpdatesSynchronizationState syncState = new UpdatesSynchronizationState();
|
||||
Consumer<IdExecutionService.ExpressionCall> funCallCallback =
|
||||
(value) -> context.getLogger().finest("ON_CACHED_CALL " + value.getExpressionId());
|
||||
Consumer<IdExecutionService.ExpressionValue> onComputedCallback =
|
||||
(value) -> context.getLogger().finest("ON_COMPUTED " + value.getExpressionId());
|
||||
Consumer<IdExecutionService.ExpressionValue> onCachedCallback =
|
||||
(value) -> context.getLogger().finest("ON_CACHED_VALUE " + value.getExpressionId());
|
||||
Consumer<Exception> onExceptionalCallback =
|
||||
(value) -> context.getLogger().finest("ON_ERROR " + value);
|
||||
|
||||
Optional<EventBinding<ExecutionEventListener>> listener =
|
||||
idExecutionInstrument.map(
|
||||
service ->
|
||||
service.bind(
|
||||
module,
|
||||
entryCallTarget,
|
||||
cache,
|
||||
methodCallsCache,
|
||||
syncState,
|
||||
nextExecutionItem,
|
||||
funCallCallback,
|
||||
onComputedCallback,
|
||||
onCachedCallback,
|
||||
onExceptionalCallback));
|
||||
Object p = context.getThreadManager().enter();
|
||||
try {
|
||||
return interopLibrary.execute(function, argument);
|
||||
} finally {
|
||||
context.getThreadManager().leave(p);
|
||||
listener.ifPresent(EventBinding::dispose);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a module at a given path to use a literal source.
|
||||
*
|
||||
|
@ -29,7 +29,7 @@ object CacheInvalidation {
|
||||
sealed trait IndexSelector
|
||||
object IndexSelector {
|
||||
|
||||
/** Invalidate value from indexes. */
|
||||
/** Invalidate value from all indexes. */
|
||||
case object All extends IndexSelector
|
||||
|
||||
/** Invalidate the types index. */
|
||||
@ -118,6 +118,38 @@ object CacheInvalidation {
|
||||
): Unit =
|
||||
instructions.foreach(run(stack, _))
|
||||
|
||||
/** Run a sequence of invalidation instructions on all visualisations.
|
||||
*
|
||||
* @param visualisations the list of available visualisations
|
||||
* @param instructions the list of cache invalidation instructions
|
||||
*/
|
||||
def runAllVisualisations(
|
||||
visualisations: Iterable[Visualisation],
|
||||
instructions: Iterable[CacheInvalidation]
|
||||
): Unit =
|
||||
instructions.foreach { instruction =>
|
||||
runVisualisations(
|
||||
visualisations,
|
||||
instruction.command,
|
||||
instruction.indexes
|
||||
)
|
||||
}
|
||||
|
||||
/** Run cache invalidation of a multiple visualisations
|
||||
*
|
||||
* @param visualisations visualisations cache should be invalidated
|
||||
* @param command the invalidation instruction
|
||||
* @param indexes the list of indexes to invalidate
|
||||
*/
|
||||
def runVisualisations(
|
||||
visualisations: Iterable[Visualisation],
|
||||
command: Command,
|
||||
indexes: Set[IndexSelector] = Set()
|
||||
): Unit =
|
||||
visualisations.foreach { visualisation =>
|
||||
run(visualisation.cache, command, indexes)
|
||||
}
|
||||
|
||||
/** Run a cache invalidation instruction on an execution stack.
|
||||
*
|
||||
* @param stack the runtime stack
|
||||
@ -148,6 +180,36 @@ object CacheInvalidation {
|
||||
frames.foreach(frame => run(frame.cache, frame.syncState, command, indexes))
|
||||
}
|
||||
|
||||
/** Run cache invalidation of a single instrument frame.
|
||||
*
|
||||
* @param cache the cache to invalidate
|
||||
* @param command the invalidation instruction
|
||||
* @param indexes the list of indexes to invalidate
|
||||
*/
|
||||
private def run(
|
||||
cache: RuntimeCache,
|
||||
command: Command,
|
||||
indexes: Set[IndexSelector]
|
||||
): Unit =
|
||||
command match {
|
||||
case Command.InvalidateAll =>
|
||||
cache.clear()
|
||||
indexes.foreach(clearIndex(_, cache))
|
||||
case Command.InvalidateKeys(keys) =>
|
||||
keys.foreach { key =>
|
||||
cache.remove(key)
|
||||
indexes.foreach(clearIndexKey(key, _, cache))
|
||||
}
|
||||
case Command.InvalidateStale(scope) =>
|
||||
val staleKeys = cache.getKeys.asScala.diff(scope.toSet)
|
||||
staleKeys.foreach { key =>
|
||||
cache.remove(key)
|
||||
indexes.foreach(clearIndexKey(key, _, cache))
|
||||
}
|
||||
case Command.SetMetadata(metadata) =>
|
||||
cache.setWeights(metadata.asJavaWeights)
|
||||
}
|
||||
|
||||
/** Run cache invalidation of a single instrument frame.
|
||||
*
|
||||
* @param cache the cache to invalidate
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.enso.interpreter.instrument
|
||||
|
||||
import org.enso.pkg.QualifiedName
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{
|
||||
ContextId,
|
||||
ExpressionId,
|
||||
@ -7,7 +8,7 @@ import org.enso.polyglot.runtime.Runtime.Api.{
|
||||
VisualisationId
|
||||
}
|
||||
|
||||
import scala.collection.mutable.Stack
|
||||
import scala.collection.mutable
|
||||
|
||||
/** Storage for active execution contexts.
|
||||
*/
|
||||
@ -50,16 +51,17 @@ class ExecutionContextManager {
|
||||
* @param id the context id.
|
||||
* @return the stack.
|
||||
*/
|
||||
def getStack(id: ContextId): Stack[InstrumentFrame] =
|
||||
def getStack(id: ContextId): mutable.Stack[InstrumentFrame] =
|
||||
synchronized {
|
||||
contexts(id).stack
|
||||
}
|
||||
|
||||
/** Gets all execution contexts.
|
||||
*
|
||||
* @return all currently available execution contexsts.
|
||||
* @return all currently available execution contexts.
|
||||
*/
|
||||
def getAll: collection.MapView[ContextId, Stack[InstrumentFrame]] =
|
||||
def getAllContexts
|
||||
: collection.MapView[ContextId, mutable.Stack[InstrumentFrame]] =
|
||||
synchronized {
|
||||
contexts.view.mapValues(_.stack)
|
||||
}
|
||||
@ -114,6 +116,22 @@ class ExecutionContextManager {
|
||||
state.visualisations.upsert(visualisation)
|
||||
}
|
||||
|
||||
/** Get visualizations of all execution contexts. */
|
||||
def getAllVisualisations: Iterable[Visualisation] =
|
||||
synchronized {
|
||||
contexts.values.flatMap(_.visualisations.getAll)
|
||||
}
|
||||
|
||||
/** Get visualisations defined in the module.
|
||||
*
|
||||
* @param module the qualified module name
|
||||
* @return the list of matching visualisations
|
||||
*/
|
||||
def getVisualisations(module: QualifiedName): Iterable[Visualisation] =
|
||||
synchronized {
|
||||
contexts.values.flatMap(_.visualisations.findByModule(module))
|
||||
}
|
||||
|
||||
/** Returns a visualisation with the provided id.
|
||||
*
|
||||
* @param contextId the identifier of the execution context
|
||||
@ -148,6 +166,25 @@ class ExecutionContextManager {
|
||||
} yield visualisation
|
||||
}
|
||||
|
||||
/** Get all visualisations invalidated by the provided list of expressions.
|
||||
*
|
||||
* @param module the module containing the visualisations
|
||||
* @param invalidatedExpressions the list of invalidated expressions
|
||||
* @return a list of matching visualisation
|
||||
*/
|
||||
def getInvalidatedVisualisations(
|
||||
module: QualifiedName,
|
||||
invalidatedExpressions: Set[ExpressionId]
|
||||
): Iterable[Visualisation] = {
|
||||
for {
|
||||
state <- contexts.values
|
||||
visualisation <- state.visualisations.findByModule(module)
|
||||
if visualisation.visualisationExpressionId.exists(
|
||||
invalidatedExpressions.contains
|
||||
)
|
||||
} yield visualisation
|
||||
}
|
||||
|
||||
/** Removes a visualisation from the holder.
|
||||
*
|
||||
* @param contextId the identifier of the execution context
|
||||
|
@ -37,6 +37,11 @@ case class InstrumentFrame(
|
||||
|
||||
case object InstrumentFrame {
|
||||
|
||||
/** Create an instrument frame.
|
||||
*
|
||||
* @param item the stack item
|
||||
* @return an instance of [[InstrumentFrame]]
|
||||
*/
|
||||
def apply(item: StackItem): InstrumentFrame =
|
||||
new InstrumentFrame(item, new RuntimeCache, new UpdatesSynchronizationState)
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
package org.enso.interpreter.instrument
|
||||
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{ExpressionId, VisualisationId}
|
||||
import org.enso.interpreter.runtime.Module
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{
|
||||
ExpressionId,
|
||||
VisualisationConfiguration,
|
||||
VisualisationId
|
||||
}
|
||||
|
||||
/** An object containing visualisation data.
|
||||
*
|
||||
@ -12,5 +17,9 @@ import org.enso.polyglot.runtime.Runtime.Api.{ExpressionId, VisualisationId}
|
||||
case class Visualisation(
|
||||
id: VisualisationId,
|
||||
expressionId: ExpressionId,
|
||||
cache: RuntimeCache,
|
||||
module: Module,
|
||||
config: VisualisationConfiguration,
|
||||
visualisationExpressionId: Option[ExpressionId],
|
||||
callback: AnyRef
|
||||
)
|
||||
|
@ -1,13 +1,16 @@
|
||||
package org.enso.interpreter.instrument
|
||||
|
||||
import org.enso.pkg.QualifiedName
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{ExpressionId, VisualisationId}
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/** A mutable holder of all visualisations attached to an execution context.
|
||||
*/
|
||||
class VisualisationHolder() {
|
||||
|
||||
private var visualisationMap: Map[ExpressionId, List[Visualisation]] =
|
||||
Map.empty.withDefaultValue(List.empty)
|
||||
private val visualisationMap: mutable.Map[ExpressionId, List[Visualisation]] =
|
||||
mutable.Map.empty.withDefaultValue(List.empty)
|
||||
|
||||
/** Upserts a visualisation.
|
||||
*
|
||||
@ -15,8 +18,8 @@ class VisualisationHolder() {
|
||||
*/
|
||||
def upsert(visualisation: Visualisation): Unit = {
|
||||
val visualisations = visualisationMap(visualisation.expressionId)
|
||||
val removed = visualisations.filterNot(_.id == visualisation.id)
|
||||
visualisationMap += (visualisation.expressionId -> (visualisation :: removed))
|
||||
val rest = visualisations.filterNot(_.id == visualisation.id)
|
||||
visualisationMap.update(visualisation.expressionId, visualisation :: rest)
|
||||
}
|
||||
|
||||
/** Removes a visualisation from the holder.
|
||||
@ -30,8 +33,8 @@ class VisualisationHolder() {
|
||||
expressionId: ExpressionId
|
||||
): Unit = {
|
||||
val visualisations = visualisationMap(expressionId)
|
||||
val removed = visualisations.filterNot(_.id == visualisationId)
|
||||
visualisationMap += (expressionId -> removed)
|
||||
val rest = visualisations.filterNot(_.id == visualisationId)
|
||||
visualisationMap.update(expressionId, rest)
|
||||
}
|
||||
|
||||
/** Finds all visualisations attached to an expression.
|
||||
@ -42,6 +45,14 @@ class VisualisationHolder() {
|
||||
def find(expressionId: ExpressionId): List[Visualisation] =
|
||||
visualisationMap(expressionId)
|
||||
|
||||
/** Finds all visualisations in a given module.
|
||||
*
|
||||
* @param module the qualified module name
|
||||
* @return a list of matching visualisation
|
||||
*/
|
||||
def findByModule(module: QualifiedName): Iterable[Visualisation] =
|
||||
visualisationMap.values.flatten.filter(_.module.getName == module)
|
||||
|
||||
/** Returns a visualisation with the provided id.
|
||||
*
|
||||
* @param visualisationId the identifier of visualisation
|
||||
@ -50,12 +61,14 @@ class VisualisationHolder() {
|
||||
def getById(visualisationId: VisualisationId): Option[Visualisation] =
|
||||
visualisationMap.values.flatten.find(_.id == visualisationId)
|
||||
|
||||
/** @return all available visualisations. */
|
||||
def getAll: Iterable[Visualisation] =
|
||||
visualisationMap.values.flatten
|
||||
}
|
||||
|
||||
object VisualisationHolder {
|
||||
|
||||
/** Returns an empty holder.
|
||||
*/
|
||||
/** Returns an empty visualisation holder. */
|
||||
def empty = new VisualisationHolder()
|
||||
|
||||
}
|
||||
|
@ -49,10 +49,10 @@ class AttachVisualisationCmd(
|
||||
ctx.jobProcessor.run(
|
||||
new UpsertVisualisationJob(
|
||||
maybeRequestId,
|
||||
Api.VisualisationAttached(),
|
||||
request.visualisationId,
|
||||
request.expressionId,
|
||||
request.visualisationConfig,
|
||||
Api.VisualisationAttached()
|
||||
request.visualisationConfig
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -42,7 +42,7 @@ class EditFileCmd(request: Api.EditFileNotification) extends Command(None) {
|
||||
private def executeJobs(implicit
|
||||
ctx: RuntimeContext
|
||||
): Iterable[ExecuteJob] = {
|
||||
ctx.contextManager.getAll
|
||||
ctx.contextManager.getAllContexts
|
||||
.collect {
|
||||
case (contextId, stack) if stack.nonEmpty =>
|
||||
new ExecuteJob(contextId, stack.toList)
|
||||
|
@ -60,10 +60,10 @@ class ModifyVisualisationCmd(
|
||||
ctx.jobProcessor.run(
|
||||
new UpsertVisualisationJob(
|
||||
maybeRequestId,
|
||||
Api.VisualisationModified(),
|
||||
request.visualisationId,
|
||||
visualisation.expressionId,
|
||||
request.visualisationConfig,
|
||||
Api.VisualisationModified()
|
||||
request.visualisationConfig
|
||||
)
|
||||
)
|
||||
maybeFutureExecutable flatMap {
|
||||
|
@ -39,7 +39,7 @@ class RenameProjectCmd(
|
||||
request.oldName,
|
||||
request.newName
|
||||
)
|
||||
ctx.contextManager.getAll.values
|
||||
ctx.contextManager.getAllContexts.values
|
||||
.foreach(updateMethodPointers(request.newName, _))
|
||||
reply(Api.ProjectRenamed(request.namespace, request.newName))
|
||||
logger.log(
|
||||
|
@ -47,7 +47,7 @@ class SetExpressionValueCmd(request: Api.SetExpressionValueNotification)
|
||||
private def executeJobs(implicit
|
||||
ctx: RuntimeContext
|
||||
): Iterable[ExecuteJob] = {
|
||||
ctx.contextManager.getAll
|
||||
ctx.contextManager.getAllContexts
|
||||
.collect {
|
||||
case (contextId, stack) if stack.nonEmpty =>
|
||||
new ExecuteJob(contextId, stack.toList)
|
||||
|
@ -12,10 +12,16 @@ import org.enso.interpreter.instrument.execution.{
|
||||
LocationResolver,
|
||||
RuntimeContext
|
||||
}
|
||||
import org.enso.interpreter.instrument.{CacheInvalidation, InstrumentFrame}
|
||||
import org.enso.interpreter.instrument.{
|
||||
CacheInvalidation,
|
||||
InstrumentFrame,
|
||||
Visualisation
|
||||
}
|
||||
import org.enso.interpreter.runtime.Module
|
||||
import org.enso.interpreter.service.error.ModuleNotFoundForFileException
|
||||
import org.enso.pkg.QualifiedName
|
||||
import org.enso.polyglot.runtime.Runtime.Api
|
||||
import org.enso.polyglot.runtime.Runtime.Api.StackItem
|
||||
import org.enso.text.buffer.Rope
|
||||
|
||||
import java.io.File
|
||||
@ -39,17 +45,7 @@ final class EnsureCompiledJob(protected val files: Iterable[File])
|
||||
|
||||
try {
|
||||
val compilationResult = ensureCompiledFiles(files)
|
||||
ctx.contextManager.getAll.values.foreach { stack =>
|
||||
getCacheMetadata(stack).foreach { metadata =>
|
||||
CacheInvalidation.run(
|
||||
stack,
|
||||
CacheInvalidation(
|
||||
CacheInvalidation.StackSelector.Top,
|
||||
CacheInvalidation.Command.SetMetadata(metadata)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
setCacheWeights()
|
||||
compilationResult
|
||||
} finally {
|
||||
ctx.locking.releaseWriteCompilationLock()
|
||||
@ -89,12 +85,7 @@ final class EnsureCompiledJob(protected val files: Iterable[File])
|
||||
applyEdits(new File(module.getPath)).map { changeset =>
|
||||
compile(module)
|
||||
.map { compilerResult =>
|
||||
val cacheInvalidationCommands =
|
||||
buildCacheInvalidationCommands(
|
||||
changeset,
|
||||
module.getSource.getCharacters
|
||||
)
|
||||
runInvalidationCommands(cacheInvalidationCommands)
|
||||
invalidateCaches(module, changeset)
|
||||
ctx.jobProcessor.runBackground(
|
||||
AnalyzeModuleInScopeJob(
|
||||
module.getName,
|
||||
@ -301,18 +292,44 @@ final class EnsureCompiledJob(protected val files: Iterable[File])
|
||||
|
||||
/** Run the invalidation commands.
|
||||
*
|
||||
* @param invalidationCommands the invalidation command to run
|
||||
* @param module the compiled module
|
||||
* @param changeset the changeset containing the list of invalidated expressions
|
||||
* @param ctx the runtime context
|
||||
*/
|
||||
private def runInvalidationCommands(
|
||||
invalidationCommands: Iterable[CacheInvalidation]
|
||||
private def invalidateCaches(
|
||||
module: Module,
|
||||
changeset: Changeset[_]
|
||||
)(implicit ctx: RuntimeContext): Unit = {
|
||||
ctx.contextManager.getAll.values
|
||||
val invalidationCommands =
|
||||
buildCacheInvalidationCommands(
|
||||
changeset,
|
||||
module.getSource.getCharacters
|
||||
)
|
||||
ctx.contextManager.getAllContexts.values
|
||||
.foreach { stack =>
|
||||
if (stack.nonEmpty) {
|
||||
if (stack.nonEmpty && isStackInModule(module.getName, stack)) {
|
||||
CacheInvalidation.runAll(stack, invalidationCommands)
|
||||
}
|
||||
}
|
||||
CacheInvalidation.runAllVisualisations(
|
||||
ctx.contextManager.getVisualisations(module.getName),
|
||||
invalidationCommands
|
||||
)
|
||||
|
||||
val invalidatedVisualisations =
|
||||
ctx.contextManager.getInvalidatedVisualisations(
|
||||
module.getName,
|
||||
changeset.invalidated
|
||||
)
|
||||
invalidatedVisualisations.foreach { visualisation =>
|
||||
UpsertVisualisationJob.upsertVisualisation(visualisation)
|
||||
}
|
||||
if (invalidatedVisualisations.nonEmpty) {
|
||||
ctx.executionService.getLogger.log(
|
||||
Level.FINE,
|
||||
s"Invalidated visualisations [${invalidatedVisualisations.map(_.id)}]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Send notification about the compilation status.
|
||||
@ -325,7 +342,7 @@ final class EnsureCompiledJob(protected val files: Iterable[File])
|
||||
diagnostics: Seq[Api.ExecutionResult.Diagnostic]
|
||||
)(implicit ctx: RuntimeContext): Unit =
|
||||
if (diagnostics.nonEmpty) {
|
||||
ctx.contextManager.getAll.keys.foreach { contextId =>
|
||||
ctx.contextManager.getAllContexts.keys.foreach { contextId =>
|
||||
ctx.endpoint.sendToClient(
|
||||
Api.Response(Api.ExecutionUpdate(contextId, diagnostics))
|
||||
)
|
||||
@ -340,7 +357,7 @@ final class EnsureCompiledJob(protected val files: Iterable[File])
|
||||
private def sendFailureUpdate(
|
||||
failure: Api.ExecutionResult.Failure
|
||||
)(implicit ctx: RuntimeContext): Unit =
|
||||
ctx.contextManager.getAll.keys.foreach { contextId =>
|
||||
ctx.contextManager.getAllContexts.keys.foreach { contextId =>
|
||||
ctx.endpoint.sendToClient(
|
||||
Api.Response(Api.ExecutionFailed(contextId, failure))
|
||||
)
|
||||
@ -354,6 +371,27 @@ final class EnsureCompiledJob(protected val files: Iterable[File])
|
||||
else
|
||||
CompilationStatus.Success
|
||||
|
||||
private def setCacheWeights()(implicit ctx: RuntimeContext): Unit = {
|
||||
ctx.contextManager.getAllContexts.values.foreach { stack =>
|
||||
getCacheMetadata(stack).foreach { metadata =>
|
||||
CacheInvalidation.run(
|
||||
stack,
|
||||
CacheInvalidation(
|
||||
CacheInvalidation.StackSelector.Top,
|
||||
CacheInvalidation.Command.SetMetadata(metadata)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
val visualisations = ctx.contextManager.getAllVisualisations
|
||||
visualisations.flatMap(getCacheMetadata).foreach { metadata =>
|
||||
CacheInvalidation.runVisualisations(
|
||||
visualisations,
|
||||
CacheInvalidation.Command.SetMetadata(metadata)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def getCacheMetadata(
|
||||
stack: Iterable[InstrumentFrame]
|
||||
)(implicit ctx: RuntimeContext): Option[CachePreferenceAnalysis.Metadata] =
|
||||
@ -370,12 +408,37 @@ final class EnsureCompiledJob(protected val files: Iterable[File])
|
||||
case _ => None
|
||||
}
|
||||
|
||||
private def getCacheMetadata(
|
||||
visualisation: Visualisation
|
||||
): Option[CachePreferenceAnalysis.Metadata] = {
|
||||
val module = visualisation.module
|
||||
module.getIr.getMetadata(CachePreferenceAnalysis)
|
||||
}
|
||||
|
||||
/** Get all modules in the current compiler scope. */
|
||||
private def getModulesInScope(implicit
|
||||
ctx: RuntimeContext
|
||||
): Iterable[Module] =
|
||||
ctx.executionService.getContext.getTopScope.getModules.asScala
|
||||
|
||||
/** Check if stack belongs to the provided module.
|
||||
*
|
||||
* @param module the qualified module name
|
||||
* @param stack the execution stack
|
||||
*/
|
||||
private def isStackInModule(
|
||||
module: QualifiedName,
|
||||
stack: Iterable[InstrumentFrame]
|
||||
): Boolean =
|
||||
stack.headOption match {
|
||||
case Some(
|
||||
InstrumentFrame(StackItem.ExplicitCall(methodPointer, _, _), _, _)
|
||||
) =>
|
||||
methodPointer.module == module.toString
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object EnsureCompiledJob {
|
||||
|
@ -429,12 +429,14 @@ object ProgramExecutionSupport {
|
||||
Either
|
||||
.catchNonFatal {
|
||||
ctx.executionService.getLogger.log(
|
||||
Level.FINEST,
|
||||
Level.FINE,
|
||||
s"Executing visualisation ${visualisation.expressionId}"
|
||||
)
|
||||
ctx.executionService.callFunction(
|
||||
ctx.executionService.callFunctionWithInstrument(
|
||||
visualisation.module,
|
||||
visualisation.callback,
|
||||
expressionValue
|
||||
expressionValue,
|
||||
visualisation.cache
|
||||
)
|
||||
}
|
||||
.flatMap {
|
||||
|
@ -1,42 +1,45 @@
|
||||
package org.enso.interpreter.instrument.job
|
||||
|
||||
import java.util.logging.Level
|
||||
|
||||
import cats.implicits._
|
||||
import org.enso.interpreter.instrument.{InstrumentFrame, Visualisation}
|
||||
import org.enso.compiler.core.IR
|
||||
import org.enso.compiler.pass.analyse.CachePreferenceAnalysis
|
||||
import org.enso.interpreter.instrument.execution.{Executable, RuntimeContext}
|
||||
import org.enso.interpreter.instrument.job.UpsertVisualisationJob.{
|
||||
EvalFailure,
|
||||
EvaluationFailed,
|
||||
MaxEvaluationRetryCount,
|
||||
EvaluationResult,
|
||||
ModuleNotFound
|
||||
}
|
||||
import org.enso.interpreter.instrument.{
|
||||
CacheInvalidation,
|
||||
InstrumentFrame,
|
||||
RuntimeCache,
|
||||
Visualisation
|
||||
}
|
||||
import org.enso.interpreter.runtime.Module
|
||||
import org.enso.interpreter.runtime.control.ThreadInterruptedException
|
||||
import org.enso.polyglot.runtime.Runtime.Api.{
|
||||
ExpressionId,
|
||||
RequestId,
|
||||
VisualisationId
|
||||
}
|
||||
import org.enso.pkg.QualifiedName
|
||||
import org.enso.polyglot.runtime.Runtime.Api._
|
||||
import org.enso.polyglot.runtime.Runtime.{Api, ApiResponse}
|
||||
|
||||
import java.util.logging.Level
|
||||
|
||||
/** A job that upserts a visualisation.
|
||||
*
|
||||
* @param requestId maybe a request id
|
||||
* @param response a response used to reply to a client
|
||||
* @param visualisationId an identifier of visualisation
|
||||
* @param expressionId an identifier of expression
|
||||
* @param config a visualisation config
|
||||
* @param response a response used to reply to a client
|
||||
*/
|
||||
class UpsertVisualisationJob(
|
||||
requestId: Option[RequestId],
|
||||
response: ApiResponse,
|
||||
visualisationId: VisualisationId,
|
||||
expressionId: ExpressionId,
|
||||
config: Api.VisualisationConfiguration,
|
||||
response: ApiResponse
|
||||
config: Api.VisualisationConfiguration
|
||||
) extends Job[Option[Executable]](
|
||||
List(config.executionContextId),
|
||||
true,
|
||||
false,
|
||||
false
|
||||
) {
|
||||
|
||||
@ -46,24 +49,36 @@ class UpsertVisualisationJob(
|
||||
ctx.locking.acquireWriteCompilationLock()
|
||||
try {
|
||||
val maybeCallable =
|
||||
evaluateExpression(config.visualisationModule, config.expression)
|
||||
UpsertVisualisationJob.evaluateVisualisationExpression(
|
||||
config.expression
|
||||
)
|
||||
|
||||
maybeCallable match {
|
||||
case Left(ModuleNotFound) =>
|
||||
replyWithModuleNotFoundError()
|
||||
case Left(ModuleNotFound(moduleName)) =>
|
||||
replyWithModuleNotFoundError(moduleName)
|
||||
None
|
||||
|
||||
case Left(EvaluationFailed(message, result)) =>
|
||||
replyWithExpressionFailedError(message, result)
|
||||
None
|
||||
|
||||
case Right(callable) =>
|
||||
val visualisation = updateVisualisation(callable)
|
||||
case Right(EvaluationResult(module, callable)) =>
|
||||
val visualisation =
|
||||
UpsertVisualisationJob.updateVisualisation(
|
||||
visualisationId,
|
||||
expressionId,
|
||||
module,
|
||||
config,
|
||||
callable
|
||||
)
|
||||
ctx.endpoint.sendToClient(Api.Response(requestId, response))
|
||||
val stack = ctx.contextManager.getStack(config.executionContextId)
|
||||
val cachedValue = stack.headOption
|
||||
.flatMap(frame => Option(frame.cache.get(expressionId)))
|
||||
requireVisualisationSynchronization(stack, expressionId)
|
||||
UpsertVisualisationJob.requireVisualisationSynchronization(
|
||||
stack,
|
||||
expressionId
|
||||
)
|
||||
cachedValue match {
|
||||
case Some(value) =>
|
||||
ProgramExecutionSupport.sendVisualisationUpdate(
|
||||
@ -84,28 +99,6 @@ class UpsertVisualisationJob(
|
||||
}
|
||||
}
|
||||
|
||||
private def requireVisualisationSynchronization(
|
||||
stack: Iterable[InstrumentFrame],
|
||||
expressionId: ExpressionId
|
||||
): Unit = {
|
||||
stack.foreach(_.syncState.setVisualisationUnsync(expressionId))
|
||||
}
|
||||
|
||||
private def updateVisualisation(
|
||||
callable: AnyRef
|
||||
)(implicit ctx: RuntimeContext): Visualisation = {
|
||||
val visualisation = Visualisation(
|
||||
visualisationId,
|
||||
expressionId,
|
||||
callable
|
||||
)
|
||||
ctx.contextManager.upsertVisualisation(
|
||||
config.executionContextId,
|
||||
visualisation
|
||||
)
|
||||
visualisation
|
||||
}
|
||||
|
||||
private def replyWithExpressionFailedError(
|
||||
message: String,
|
||||
executionResult: Option[Api.ExecutionResult.Diagnostic]
|
||||
@ -118,39 +111,115 @@ class UpsertVisualisationJob(
|
||||
)
|
||||
}
|
||||
|
||||
private def replyWithModuleNotFoundError()(implicit
|
||||
private def replyWithModuleNotFoundError(module: String)(implicit
|
||||
ctx: RuntimeContext
|
||||
): Unit = {
|
||||
ctx.endpoint.sendToClient(
|
||||
Api.Response(
|
||||
requestId,
|
||||
Api.ModuleNotFound(config.visualisationModule)
|
||||
)
|
||||
Api.Response(requestId, Api.ModuleNotFound(module))
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object UpsertVisualisationJob {
|
||||
|
||||
/** The number of times to retry the expression evaluation. */
|
||||
val MaxEvaluationRetryCount: Int = 5
|
||||
|
||||
/** Base trait for evaluation failures.
|
||||
*/
|
||||
sealed trait EvaluationFailure
|
||||
|
||||
/** Signals that a module cannot be found.
|
||||
*
|
||||
* @param moduleName the module name
|
||||
*/
|
||||
case class ModuleNotFound(moduleName: String) extends EvaluationFailure
|
||||
|
||||
/** Signals that an evaluation of an expression failed.
|
||||
*
|
||||
* @param message the textual reason of a failure
|
||||
* @param failure the error description
|
||||
*/
|
||||
case class EvaluationFailed(
|
||||
message: String,
|
||||
failure: Option[Api.ExecutionResult.Diagnostic]
|
||||
) extends EvaluationFailure
|
||||
|
||||
case class EvaluationResult(module: Module, callback: AnyRef)
|
||||
|
||||
/** Upsert the provided visualisation.
|
||||
*
|
||||
* @param visualisation the visualisation to update
|
||||
*/
|
||||
def upsertVisualisation(
|
||||
visualisation: Visualisation
|
||||
)(implicit ctx: RuntimeContext): Unit = {
|
||||
val visualisationConfig = visualisation.config
|
||||
val expressionId = visualisation.expressionId
|
||||
val visualisationId = visualisation.id
|
||||
val maybeCallable =
|
||||
evaluateVisualisationExpression(visualisation.config.expression)
|
||||
|
||||
maybeCallable.foreach { result =>
|
||||
updateVisualisation(
|
||||
visualisationId,
|
||||
expressionId,
|
||||
result.module,
|
||||
visualisationConfig,
|
||||
result.callback
|
||||
)
|
||||
val stack =
|
||||
ctx.contextManager.getStack(visualisationConfig.executionContextId)
|
||||
requireVisualisationSynchronization(stack, expressionId)
|
||||
}
|
||||
}
|
||||
|
||||
/** Find module by name.
|
||||
*
|
||||
* @param moduleName the module name
|
||||
* @return either the requested module or an error
|
||||
*/
|
||||
private def findModule(
|
||||
moduleName: String
|
||||
)(implicit ctx: RuntimeContext): Either[EvalFailure, Module] = {
|
||||
)(implicit ctx: RuntimeContext): Either[EvaluationFailure, Module] = {
|
||||
val context = ctx.executionService.getContext
|
||||
// TODO [RW] more specific error when the module cannot be installed (#1861)
|
||||
context.ensureModuleIsLoaded(moduleName)
|
||||
val maybeModule = context.findModule(moduleName)
|
||||
|
||||
if (maybeModule.isPresent) Right(maybeModule.get())
|
||||
else Left(ModuleNotFound)
|
||||
else Left(ModuleNotFound(moduleName))
|
||||
}
|
||||
|
||||
/** Evaluate the visualisation expression in a given module.
|
||||
*
|
||||
* @param module the module where to evaluate the expression
|
||||
* @param expression the visualisation expression
|
||||
* @param retryCount the number of attempted retries
|
||||
* @return either the evaluation result or an evaluation failure
|
||||
*/
|
||||
private def evaluateModuleExpression(
|
||||
module: Module,
|
||||
expression: String,
|
||||
expression: Api.VisualisationExpression,
|
||||
retryCount: Int = 0
|
||||
)(implicit
|
||||
ctx: RuntimeContext
|
||||
): Either[EvalFailure, AnyRef] =
|
||||
): Either[EvaluationFailure, EvaluationResult] =
|
||||
Either
|
||||
.catchNonFatal {
|
||||
val callback = expression match {
|
||||
case Api.VisualisationExpression.Text(_, expression) =>
|
||||
ctx.executionService.evaluateExpression(module, expression)
|
||||
case Api.VisualisationExpression.ModuleMethod(
|
||||
Api.MethodPointer(_, definedOnType, name)
|
||||
) =>
|
||||
ctx.executionService.prepareFunctionCall(
|
||||
module,
|
||||
QualifiedName.fromString(definedOnType).item,
|
||||
name
|
||||
)
|
||||
}
|
||||
EvaluationResult(module, callback)
|
||||
}
|
||||
.leftFlatMap {
|
||||
case _: ThreadInterruptedException
|
||||
@ -193,38 +262,110 @@ class UpsertVisualisationJob(
|
||||
)
|
||||
}
|
||||
|
||||
private def evaluateExpression(
|
||||
moduleName: String,
|
||||
expression: String
|
||||
)(implicit ctx: RuntimeContext): Either[EvalFailure, AnyRef] =
|
||||
/** Evaluate the visualisation expression.
|
||||
*
|
||||
* @param expression the visualisation expression to evaluate
|
||||
* @return either the evaluation result or an evaluation error
|
||||
*/
|
||||
private def evaluateVisualisationExpression(
|
||||
expression: Api.VisualisationExpression
|
||||
)(implicit
|
||||
ctx: RuntimeContext
|
||||
): Either[EvaluationFailure, EvaluationResult] = {
|
||||
for {
|
||||
module <- findModule(moduleName)
|
||||
module <- findModule(expression.module)
|
||||
expression <- evaluateModuleExpression(module, expression)
|
||||
} yield expression
|
||||
|
||||
}
|
||||
|
||||
object UpsertVisualisationJob {
|
||||
|
||||
/** The number of times to retry the expression evaluation. */
|
||||
val MaxEvaluationRetryCount: Int = 5
|
||||
|
||||
/** Base trait for evaluation failures.
|
||||
*/
|
||||
sealed trait EvalFailure
|
||||
|
||||
/** Signals that a module cannot be found.
|
||||
*/
|
||||
case object ModuleNotFound extends EvalFailure
|
||||
|
||||
/** Signals that an evaluation of an expression failed.
|
||||
/** Update the visualisation state.
|
||||
*
|
||||
* @param message the textual reason of a failure
|
||||
* @param failure the error description
|
||||
* @param visualisationId the visualisation identifier
|
||||
* @param expressionId the expression to which the visualisation is applied
|
||||
* @param module the module containing the visualisation
|
||||
* @param visualisationConfig the visualisation configuration
|
||||
* @param callback the visualisation callback function
|
||||
* @param ctx the runtime context
|
||||
* @return the re-evaluated visualisation
|
||||
*/
|
||||
case class EvaluationFailed(
|
||||
message: String,
|
||||
failure: Option[Api.ExecutionResult.Diagnostic]
|
||||
) extends EvalFailure
|
||||
private def updateVisualisation(
|
||||
visualisationId: VisualisationId,
|
||||
expressionId: ExpressionId,
|
||||
module: Module,
|
||||
visualisationConfig: VisualisationConfiguration,
|
||||
callback: AnyRef
|
||||
)(implicit ctx: RuntimeContext): Visualisation = {
|
||||
val visualisationExpressionId =
|
||||
findVisualisationExpressionId(module, visualisationConfig.expression)
|
||||
val visualisation = Visualisation(
|
||||
visualisationId,
|
||||
expressionId,
|
||||
new RuntimeCache(),
|
||||
module,
|
||||
visualisationConfig,
|
||||
visualisationExpressionId,
|
||||
callback
|
||||
)
|
||||
setCacheWeights(visualisation)
|
||||
ctx.contextManager.upsertVisualisation(
|
||||
visualisationConfig.executionContextId,
|
||||
visualisation
|
||||
)
|
||||
visualisation
|
||||
}
|
||||
|
||||
/** Find the expressionId of visualisation function.
|
||||
*
|
||||
* @param module the module environment
|
||||
* @param visualisationExpression the visualisation expression
|
||||
* @return the expression id of required visualisation function
|
||||
*/
|
||||
private def findVisualisationExpressionId(
|
||||
module: Module,
|
||||
visualisationExpression: VisualisationExpression
|
||||
): Option[ExpressionId] =
|
||||
visualisationExpression match {
|
||||
case VisualisationExpression.ModuleMethod(methodPointer) =>
|
||||
module.getIr.bindings
|
||||
.collect { case method: IR.Module.Scope.Definition.Method =>
|
||||
val methodReference = method.methodReference
|
||||
val methodReferenceName = methodReference.methodName.name
|
||||
val methodReferenceTypeOpt = methodReference.typePointer.map(_.name)
|
||||
|
||||
method.getExternalId.filter { _ =>
|
||||
methodReferenceName == methodPointer.name &&
|
||||
methodReferenceTypeOpt.isEmpty
|
||||
}
|
||||
}
|
||||
.flatten
|
||||
.headOption
|
||||
|
||||
case _: VisualisationExpression.Text => None
|
||||
}
|
||||
|
||||
/** Set the cache weights for the provided visualisation.
|
||||
*
|
||||
* @param visualisation the visualisation to update
|
||||
*/
|
||||
private def setCacheWeights(visualisation: Visualisation): Unit = {
|
||||
visualisation.module.getIr.getMetadata(CachePreferenceAnalysis).foreach {
|
||||
metadata =>
|
||||
CacheInvalidation.runVisualisations(
|
||||
Seq(visualisation),
|
||||
CacheInvalidation.Command.SetMetadata(metadata)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Require to send the visualisation update.
|
||||
*
|
||||
* @param stack the execution stack
|
||||
* @param expressionId the expression id to which the visualisation is applied
|
||||
*/
|
||||
private def requireVisualisationSynchronization(
|
||||
stack: Iterable[InstrumentFrame],
|
||||
expressionId: ExpressionId
|
||||
): Unit =
|
||||
stack.foreach(_.syncState.setVisualisationUnsync(expressionId))
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user