Skip to content
Open
2 changes: 1 addition & 1 deletion catroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ android {
release {
buildConfigField "boolean", "FEATURE_MERGE_ENABLED", "false"
buildConfigField "boolean", "FEATURE_POCKETMUSIC_ENABLED", "false"
buildConfigField "boolean", "FEATURE_USER_REPORTERS_ENABLED", "false"
buildConfigField "boolean", "FEATURE_USER_REPORTERS_ENABLED", "true"
resValue "string", "SNACKBAR_HINTS_ENABLED", "true"
signingConfig signingConfigs.debug
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1488,13 +1488,14 @@ public Action createStopScriptAction(int spinnerSelection, Script currentScript,
}
}

public Action createReportAction(Sprite sprite, SequenceAction sequence, Script currentScript, Formula reportFormula) {
public Action createReportAction(Sprite sprite, ScriptSequenceAction sequence, Script currentScript, Formula reportFormula) {
if (currentScript instanceof UserDefinedScript) {
ReportAction reportAction = Actions.action(ReportAction.class);
Scope scope = new Scope(ProjectManager.getInstance().getCurrentProject(), sprite, sequence);
reportAction.setScope(scope);
reportAction.setCurrentScript(currentScript);
reportAction.setReportFormula(reportFormula);
reportAction.setCallId(sequence.callId);
return reportAction;
} else {
StopThisScriptAction stopThisScriptAction = Actions.action(StopThisScriptAction.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class EventWrapperListener internal constructor(private val look: Look) : EventL
scriptClone.setUserDefinedBrickInputs((event.eventId as
UserDefinedBrickEventId).userBrickParameters)
val sequenceClone: ScriptSequenceAction = sequenceAction.cloneAndChangeScript(scriptClone)
sequenceClone.callId = (event.eventId as UserDefinedBrickEventId).callId
sequenceClone.script.run(sprite, sequenceClone)
event.addSpriteToWaitList(sprite)
startThread(ScriptSequenceActionWithWaiter(sequenceClone, event, sprite))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
private Script currentScript;
private Formula reportFormula;
private Scope scope;
private long callId = -1;

public void setScope(Scope scope) {
this.scope = scope;
Expand All @@ -48,6 +49,10 @@
this.reportFormula = reportFormula;
}

public void setCallId(long callId) {
this.callId = callId;
}

public Formula getReportFormula() {
return this.reportFormula;
}
Expand All @@ -58,8 +63,12 @@

@Override
public boolean act(float delta) {
if (actor instanceof Look) {
((Look) actor).stopThreadWithScript(currentScript);
if (callId != -1) {
Object value = reportFormula.interpretObject(scope);
org.catrobat.catroid.formulaeditor.FunctionCallManager.getInstance().setResult(callId, value);
}
if (actor instanceof org.catrobat.catroid.content.Look) {

Check warning on line 70 in catroid/src/main/java/org/catrobat/catroid/content/actions/ReportAction.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this instanceof check and cast with 'instanceof Look look'

See more on https://sonarcloud.io/project/issues?id=Catrobat_Catroid&issues=AZ0xPWxSf6sTKoBggT10&open=AZ0xPWxSf6sTKoBggT10&pullRequest=5174
((org.catrobat.catroid.content.Look) actor).stopThreadWithScript(currentScript);
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

public class ScriptSequenceAction extends SequenceAction {
protected final Script script;
public long callId = -1;

Check warning on line 36 in catroid/src/main/java/org/catrobat/catroid/content/actions/ScriptSequenceAction.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make callId a static final constant or non-public and provide accessors if needed.

See more on https://sonarcloud.io/project/issues?id=Catrobat_Catroid&issues=AZ0xPWzqf6sTKoBggT11&open=AZ0xPWzqf6sTKoBggT11&pullRequest=5174

public ScriptSequenceAction(@NonNull Script script) {
this.script = script;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public int getViewResource() {
@Override
public void addActionToSequence(Sprite sprite, ScriptSequenceAction sequence) {
sequence.addAction(sprite.getActionFactory()
.createReportAction(sprite, new SequenceAction(), sequence.getScript(),
.createReportAction(sprite, sequence, sequence.getScript(),
getFormulaWithBrickField(BrickField.REPORT_BRICK)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,22 @@
public class UserDefinedBrickEventId extends EventId {
public final UUID userDefinedBrickID;
public List<Object> userBrickParameters;
public long callId = -1;

Check warning on line 32 in catroid/src/main/java/org/catrobat/catroid/content/eventids/UserDefinedBrickEventId.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make callId a static final constant or non-public and provide accessors if needed.

See more on https://sonarcloud.io/project/issues?id=Catrobat_Catroid&issues=AZ0xPWz5f6sTKoBggT12&open=AZ0xPWz5f6sTKoBggT12&pullRequest=5174

public UserDefinedBrickEventId(UUID userDefinedBrickID,
List<Object> userBrickParameters) {
List<Object> userBrickParameters, long callId) {
this.userDefinedBrickID = userDefinedBrickID;
this.userBrickParameters = userBrickParameters;
this.callId = callId;
}

public UserDefinedBrickEventId(UUID userDefinedBrickID,
List<Object> userBrickParameters) {
this(userDefinedBrickID, userBrickParameters, -1);
}

public UserDefinedBrickEventId(UUID userDefinedBrickID) {
this.userDefinedBrickID = userDefinedBrickID;
this.userBrickParameters = null;
this(userDefinedBrickID, null, -1);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
package org.catrobat.catroid.formulaeditor;

import org.catrobat.catroid.ProjectManager;
import org.catrobat.catroid.content.EventWrapper;
import org.catrobat.catroid.content.Project;
import org.catrobat.catroid.content.Scene;
import org.catrobat.catroid.content.Scope;
import org.catrobat.catroid.content.bricks.Brick;
import org.catrobat.catroid.content.eventids.UserDefinedBrickEventId;
import org.catrobat.catroid.formulaeditor.function.ArduinoFunctionProvider;
import org.catrobat.catroid.formulaeditor.function.BinaryFunction;
import org.catrobat.catroid.formulaeditor.function.FormulaFunction;
Expand Down Expand Up @@ -55,6 +57,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -99,7 +102,7 @@ public class FormulaElement implements Serializable {
private static final long serialVersionUID = 1L;

public enum ElementType {
OPERATOR, FUNCTION, NUMBER, SENSOR, USER_VARIABLE, USER_LIST, USER_DEFINED_BRICK_INPUT, BRACKET, STRING, COLLISION_FORMULA
OPERATOR, FUNCTION, NUMBER, SENSOR, USER_VARIABLE, USER_LIST, USER_DEFINED_BRICK_INPUT, BRACKET, STRING, COLLISION_FORMULA, USER_DEFINED_FUNCTION
}

private ElementType type;
Expand Down Expand Up @@ -207,6 +210,9 @@ public List<InternToken> getInternTokenList() {
case COLLISION_FORMULA:
addToken(tokens, COLLISION_FORMULA, value);
break;
case USER_DEFINED_FUNCTION:
addUserDefinedFunctionTokens(tokens, value, leftChild, rightChild, additionalChildren);
break;
}
return tokens;
}
Expand All @@ -231,25 +237,32 @@ private void addOperatorTokens(List<InternToken> tokens, String value) {
tryAddInternTokens(tokens, rightChild);
}

private void addUserDefinedFunctionTokens(List<InternToken> tokens, String brickId, FormulaElement leftChild,
FormulaElement rightChild, List<FormulaElement> additionalChildren) {
tokens.add(new InternToken(InternTokenType.USER_DEFINED_FUNCTION, brickId));
addParameterTokens(tokens, leftChild, rightChild, additionalChildren);
}

private void addFunctionTokens(List<InternToken> tokens, String value, FormulaElement leftChild, FormulaElement rightChild) {
addToken(tokens, FUNCTION_NAME, value);
boolean functionHasParameters = false;
addParameterTokens(tokens, leftChild, rightChild, additionalChildren);
}

private void addParameterTokens(List<InternToken> tokens, FormulaElement leftChild,
FormulaElement rightChild, List<FormulaElement> additionalChildren) {
if (leftChild != null) {
addToken(tokens, FUNCTION_PARAMETERS_BRACKET_OPEN);
functionHasParameters = true;
tokens.addAll(leftChild.getInternTokenList());
}
if (rightChild != null) {
addToken(tokens, FUNCTION_PARAMETER_DELIMITER);
tokens.addAll(rightChild.getInternTokenList());
}
for (FormulaElement child : additionalChildren) {
if (child != null) {
if (rightChild != null) {
addToken(tokens, FUNCTION_PARAMETER_DELIMITER);
tokens.addAll(child.getInternTokenList());
tokens.addAll(rightChild.getInternTokenList());
}
for (FormulaElement child : additionalChildren) {
if (child != null) {
addToken(tokens, FUNCTION_PARAMETER_DELIMITER);
tokens.addAll(child.getInternTokenList());
}
}
}
if (functionHasParameters) {
addToken(tokens, FUNCTION_PARAMETERS_BRACKET_CLOSE);
}
}
Expand Down Expand Up @@ -429,6 +442,8 @@ private Object rawInterpretRecursive(Scope scope) {
StageListener stageListener = StageActivity.stageListener;
return tryInterpretCollision(scope.getSprite().look, value, currentlyPlayingScene,
stageListener);
case USER_DEFINED_FUNCTION:
return interpretUserDefinedFunction(scope);
}
return FALSE;
}
Expand Down Expand Up @@ -525,6 +540,31 @@ private Object interpretFormulaFunction(Functions function, List<Object> argumen
return formulaFunction.execute(argumentsDouble.get(0), argumentsDouble.get(1), argumentsDouble.get(2));
}


private Object interpretUserDefinedFunction(Scope scope) {
UUID brickId = UUID.fromString(value);

List<Object> parameters = new ArrayList<>();
if (leftChild != null) {
parameters.add(leftChild.interpretRecursive(scope));
if (rightChild != null) {
parameters.add(rightChild.interpretRecursive(scope));
for (FormulaElement child : additionalChildren) {
parameters.add(child.interpretRecursive(scope));
}
}
}

long callId = FunctionCallManager.getInstance().createCall();
UserDefinedBrickEventId eventId = new UserDefinedBrickEventId(brickId, parameters, callId);

if (scope.getSprite() != null) {
scope.getSprite().look.fire(new EventWrapper(eventId, false));
}

return FunctionCallManager.getInstance().waitForResult(callId);
}

private Object interpretFunctionNumberOfItems(Object left, Scope scope) {
if (leftChild.type == ElementType.USER_LIST) {
UserList userList = UserDataWrapper.getUserList(leftChild.value, scope);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2025 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.catrobat.catroid.formulaeditor

import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicLong

class FunctionCallManager private constructor() {

private val callIdCounter = AtomicLong(0)
private val pendingCalls = ConcurrentHashMap<Long, CallResult>()

fun createCall(): Long {
val id = callIdCounter.incrementAndGet()
pendingCalls[id] = CallResult()
return id
}

fun waitForResult(callId: Long): Any {
val result = pendingCalls[callId] ?: return DEFAULT_VALUE

return try {
if (result.latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
result.value ?: DEFAULT_VALUE
} else {
DEFAULT_VALUE
}
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
DEFAULT_VALUE
} finally {
pendingCalls.remove(callId)
}
}

fun setResult(callId: Long, value: Any?) {
val result = pendingCalls[callId]
if (result != null) {
result.value = value
result.latch.countDown()
}
}

private class CallResult {
val latch = CountDownLatch(1)

@Volatile
var value: Any? = null
}

companion object {
private const val TIMEOUT_SECONDS = 30L
private const val DEFAULT_VALUE = 0.0

@JvmStatic
val instance = FunctionCallManager()

Check warning on line 79 in catroid/src/main/java/org/catrobat/catroid/formulaeditor/FunctionCallManager.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Singleton pattern should use object declarations or expressions.

See more on https://sonarcloud.io/project/issues?id=Catrobat_Catroid&issues=AZ1fWOfzrHfHDGLiJcvZ&open=AZ1fWOfzrHfHDGLiJcvZ&pullRequest=5174
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ private FormulaElement term(Scope scope) throws InternFormulaParserException {
currentElement.replaceElement(collision());
break;

case USER_DEFINED_FUNCTION:
currentElement.replaceElement(userDefinedFunction(scope));
break;

default:
throw new InternFormulaParserException("Parse Error");
}
Expand Down Expand Up @@ -331,6 +335,20 @@ private FormulaElement function(Scope scope) throws InternFormulaParserException
FormulaElement functionTree = new FormulaElement(FormulaElement.ElementType.FUNCTION, currentToken.getTokenStringValue(), null);
getNextToken();

parseParameters(functionTree, scope);
return functionTree;
}

private FormulaElement userDefinedFunction(Scope scope) throws InternFormulaParserException {
FormulaElement functionTree = new FormulaElement(FormulaElement.ElementType.USER_DEFINED_FUNCTION,
currentToken.getTokenStringValue(), null);
getNextToken();

parseParameters(functionTree, scope);
return functionTree;
}

private void parseParameters(FormulaElement functionTree, Scope scope) throws InternFormulaParserException {
if (currentToken.isFunctionParameterBracketOpen()) {
getNextToken();
functionTree.setLeftChild(termList(scope));
Expand All @@ -347,7 +365,6 @@ private FormulaElement function(Scope scope) throws InternFormulaParserException
}
getNextToken();
}
return functionTree;
}

private String number() throws InternFormulaParserException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ public boolean isString() {
return internTokenType == InternTokenType.STRING;
}

public boolean isUserDefinedFunction() {
return internTokenType == InternTokenType.USER_DEFINED_FUNCTION;
}

public void appendToTokenStringValue(String stringToAppend) {
this.tokenStringValue += stringToAppend;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@
public enum InternTokenType {
NUMBER, OPERATOR, FUNCTION_NAME, BRACKET_OPEN, BRACKET_CLOSE, SENSOR, FUNCTION_PARAMETERS_BRACKET_OPEN,
FUNCTION_PARAMETERS_BRACKET_CLOSE, FUNCTION_PARAMETER_DELIMITER, PERIOD, USER_VARIABLE,
USER_LIST, USER_DEFINED_BRICK_INPUT, COLLISION_FORMULA, STRING, PARSER_END_OF_FILE,
USER_LIST, USER_DEFINED_BRICK_INPUT, COLLISION_FORMULA, STRING, PARSER_END_OF_FILE, USER_DEFINED_FUNCTION,
}
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ open class CategoryBricksFactory {
var userDefinedBricks: MutableList<Brick> = ArrayList()
if (currentSprite != null) userDefinedBricks = currentSprite.userDefinedBrickList
userDefinedBricks = ArrayList(userDefinedBricks)
if (BuildConfig.FEATURE_USER_REPORTERS_ENABLED) userDefinedBricks.add(ReportBrick())
userDefinedBricks.add(ReportBrick())
return userDefinedBricks
}

Expand Down
Loading
Loading