提交 d362c560 编写于 作者: K kohsuke

extended LoadBalancer to work with the new paradigm.

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@34610 71c3de6d-444a-0410-be80-ed276b4c234a
上级 a41f73d1
......@@ -41,7 +41,7 @@ import hudson.model.Cause.UserCause;
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.Queue.Executable;
import hudson.model.Queue.ExecutionUnit;
import hudson.model.queue.ExecutionUnit;
import hudson.model.Queue.WaitingItem;
import hudson.model.RunMap.Constructor;
import hudson.model.labels.LabelAtom;
......@@ -915,6 +915,10 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return b.getBuiltOn();
}
public Object getSameNodeConstraint() {
return this; // in this way, any member that wants to run with the main guy can nominate the project itself
}
/**
* {@inheritDoc}
*
......
......@@ -26,6 +26,7 @@ package hudson.model;
import hudson.Util;
import hudson.model.Queue.*;
import hudson.FilePath;
import hudson.model.queue.ExecutionUnit;
import hudson.model.queue.WorkUnit;
import hudson.util.TimeUnit2;
import hudson.util.InterceptingProxy;
......
......@@ -27,10 +27,14 @@ import hudson.model.Node.Mode;
import hudson.model.Queue.ApplicableJobOfferList;
import hudson.model.Queue.JobOffer;
import hudson.model.Queue.Task;
import hudson.model.Queue.NonBlockingTask;
import hudson.model.queue.MatchingWorksheet;
import hudson.model.queue.MatchingWorksheet.ExecutorChunk;
import hudson.model.queue.MatchingWorksheet.Mapping;
import hudson.util.ConsistentHash;
import hudson.util.ConsistentHash.Hash;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
......@@ -63,6 +67,8 @@ public abstract class LoadBalancer /*implements ExtensionPoint*/ {
*/
protected abstract JobOffer choose(Task task, ApplicableJobOfferList applicable);
protected abstract Mapping map(Task task, MatchingWorksheet worksheet);
/**
* Traditional implementation of this.
*/
......@@ -118,6 +124,12 @@ public abstract class LoadBalancer /*implements ExtensionPoint*/ {
// nothing available
return null;
}
@Override
protected Mapping map(Task task, MatchingWorksheet worksheet) {
// TODO:
return CONSISTENT_HASH.map(task,worksheet);
}
};
/**
......@@ -146,6 +158,53 @@ public abstract class LoadBalancer /*implements ExtensionPoint*/ {
// nothing available
return null;
}
@Override
protected Mapping map(Task task, MatchingWorksheet ws) {
// build consistent hash for each work chunk
List<ConsistentHash<ExecutorChunk>> hashes = new ArrayList<ConsistentHash<ExecutorChunk>>(ws.works.size());
for (int i=0; i<ws.works.size(); i++) {
ConsistentHash<ExecutorChunk> hash = new ConsistentHash<ExecutorChunk>(new Hash<ExecutorChunk>() {
public String hash(ExecutorChunk node) {
return node.getName();
}
});
for (ExecutorChunk ec : ws.works(i).applicableExecutorChunks())
hash.add(ec,ec.size()*100);
hashes.add(hash);
}
// do a greedy assignment
Mapping m = ws.new Mapping();
assert m.size()==ws.works.size(); // just so that you the reader of the source code don't get confused with the for loop index
if (assignGreedily(m,task,hashes,0)) {
assert m.isCompletelyValid();
return m;
} else
return null;
}
private boolean assignGreedily(Mapping m, Task task, List<ConsistentHash<ExecutorChunk>> hashes, int i) {
if (i==hashes.size()) return true; // fully assigned
String key = task.getFullDisplayName() + (i>0 ? String.valueOf(i) : "");
for (ExecutorChunk ec : hashes.get(i).list(key)) {
// let's attempt this assignment
m.assign(i,ec);
if (m.isPartiallyValid() && assignGreedily(m,task,hashes,i+1))
return true; // successful greedily allocation
// otherwise 'ec' wasn't a good fit for us. try next.
}
// every attempt failed
m.assign(i,null);
return false;
}
};
/**
......@@ -166,6 +225,16 @@ public abstract class LoadBalancer /*implements ExtensionPoint*/ {
return base.choose(task, applicable);
}
@Override
protected Mapping map(Task task, MatchingWorksheet worksheet) {
if (Queue.ifBlockedByHudsonShutdown(task)) {
// if we are quieting down, don't start anything new so that
// all executors will be eventually free.
return null;
}
return base.map(task, worksheet);
}
/**
* Double-sanitization is pointless.
*/
......
......@@ -35,7 +35,10 @@ import static hudson.util.Iterators.reverse;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.model.queue.AbstractQueueTask;
import hudson.model.queue.ExecutionUnit;
import hudson.model.queue.FutureImpl;
import hudson.model.queue.MatchingWorksheet;
import hudson.model.queue.MatchingWorksheet.Mapping;
import hudson.model.queue.QueueSorter;
import hudson.model.queue.QueueTaskDispatcher;
import hudson.model.queue.WorkUnit;
......@@ -156,7 +159,7 @@ public class Queue extends ResourceController implements Saveable {
* <p>
* An idle executor (that calls {@link Queue#pop()} creates
* a new {@link JobOffer} and gets itself {@linkplain Queue#parked parked},
* and we'll eventually hand out an {@link #item} to build.
* and we'll eventually hand out an {@link #workUnit} to build.
*/
public class JobOffer {
public final Executor executor;
......@@ -168,18 +171,18 @@ public class Queue extends ResourceController implements Saveable {
private final OneShotEvent event = new OneShotEvent(Queue.this);
/**
* The project that this {@link Executor} is going to build.
* The work unit that this {@link Executor} is going to handle.
* (Or null, in which case event is used to trigger a queue maintenance.)
*/
private BuildableItem item;
private WorkUnit workUnit;
private JobOffer(Executor executor) {
this.executor = executor;
}
public void set(BuildableItem p) {
assert this.item == null;
this.item = p;
public void set(WorkUnit p) {
assert this.workUnit == null;
this.workUnit = p;
event.signal();
}
......@@ -204,7 +207,7 @@ public class Queue extends ResourceController implements Saveable {
* Is this executor ready to accept some tasks?
*/
public boolean isAvailable() {
return item == null && !executor.getOwner().isOffline() && executor.getOwner().isAcceptingTasks();
return workUnit == null && !executor.getOwner().isOffline() && executor.getOwner().isAcceptingTasks();
}
public Node getNode() {
......@@ -740,17 +743,21 @@ public class Queue extends ResourceController implements Saveable {
continue;
}
JobOffer runner = loadBalancer.choose(p.task, new ApplicableJobOfferList(p.task));
if (runner == null)
List<JobOffer> candidates = new ArrayList<JobOffer>(parked.size());
for (JobOffer j : parked.values())
if(j.canTake(p.task))
candidates.add(j);
Mapping m = loadBalancer.map(p.task, new MatchingWorksheet(p.task, candidates));
if (m == null)
// if we couldn't find the executor that fits,
// just leave it in the buildables list and
// check if we can execute other projects
continue;
assert runner.canTake(p.task);
// found a matching executor. use it.
runner.set(p);
m.execute(new WorkUnitContext(p));
itr.remove();
pendings.add(p);
}
......@@ -776,27 +783,24 @@ public class Queue extends ResourceController implements Saveable {
parked.remove(exec);
// am I woken up because I have a project to build?
if (offer.item != null) {
if (offer.workUnit != null) {
// if so, just build it
LOGGER.fine("Pop returning " + offer.item + " for " + exec.getName());
pendings.remove(offer.item);
LOGGER.fine("Pop returning " + offer.workUnit + " for " + exec.getName());
// TODO: I think this has to be done by the last executor that leaves the pop(), not by main executor
if (offer.workUnit.isMainWork())
pendings.remove(offer.workUnit.context.item);
WorkUnitContext wuc = new WorkUnitContext(offer.item);
return wuc.createWorkUnit(offer.item.task);
return offer.workUnit;
}
// otherwise run a queue maintenance
}
} finally {
// remove myself from the parked list
JobOffer offer = parked.remove(exec);
if (offer != null && offer.item != null) {
// we are already assigned a project,
// ask for someone else to build it.
// note that while this thread is waiting for CPU
// someone else can schedule this build again,
// so check the contains method first.
if (!contains(offer.item.task))
buildables.put(offer.item.task,offer.item);
if (offer != null && offer.workUnit != null) {
// we are already assigned a project, but now we can't handle it.
offer.workUnit.context.abort();
}
// since this executor might have been chosen for
......@@ -877,7 +881,7 @@ public class Queue extends ResourceController implements Saveable {
// no more executors will be offered job except by
// the pop() code.
for (Entry<Executor, JobOffer> av : parked.entrySet()) {
if (av.getValue().item == null) {
if (av.getValue().workUnit == null) {
av.getValue().event.signal();
return;
}
......@@ -1026,20 +1030,6 @@ public class Queue extends ResourceController implements Saveable {
* compatibility with future changes to this interface.
*/
public interface Task extends ModelObject, ExecutionUnit {
/**
* If this task needs to be run on a node with a particular label,
* return that {@link Label}. Otherwise null, indicating
* it can run on anywhere.
*/
Label getAssignedLabel();
/**
* If the previous execution of this task run on a certain node
* and this task prefers to run on the same node, return that.
* Otherwise null.
*/
Node getLastBuiltOn();
/**
* Returns true if the execution should be blocked
* for temporary reasons.
......@@ -1081,19 +1071,6 @@ public class Queue extends ResourceController implements Saveable {
*/
String getFullDisplayName();
/**
* Estimate of how long will it take to execute this task.
* Measured in milliseconds.
*
* @return -1 if it's impossible to estimate.
*/
long getEstimatedDuration();
/**
* Creates {@link Executable}, which performs the actual execution of the task.
*/
Executable createExecutable() throws IOException;
/**
* Checks the permission to see if the current user can abort this executable.
* Returns normally from this method if it's OK.
......@@ -1135,28 +1112,11 @@ public class Queue extends ResourceController implements Saveable {
*
* The collection returned by this method doesn't contain this main {@link ExecutionUnit}.
* The returned value is read-only. Can be empty but never null.
*/
Collection<? extends ExecutionUnit> getMemberExecutionUnits();
}
/**
* A component of {@link Task} that represents a computation carried out by a single {@link Executor}.
*
* A {@link Task} consists of a number of {@link ExecutionUnit}.
*/
public interface ExecutionUnit extends ResourceActivity {
/**
* Estimate of how long will it take to execute this task.
* Measured in milliseconds.
*
* @return -1 if it's impossible to estimate.
*/
long getEstimatedDuration();
/**
* Creates {@link Executable}, which performs the actual execution of the task.
*
* @since 1.FATTASK
*/
Executable createExecutable() throws IOException;
Collection<? extends ExecutionUnit> getMemberExecutionUnits();
}
/**
......
......@@ -25,7 +25,6 @@
package hudson.model.queue;
import hudson.model.Queue;
import hudson.model.Queue.ExecutionUnit;
import java.util.Collection;
import java.util.Collections;
......
/*
* The MIT License
*
* Copyright (c) 2010, InfraDNA, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model.queue;
import hudson.model.Executor;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.Queue.Executable;
import hudson.model.Queue.Task;
import hudson.model.ResourceActivity;
import java.io.IOException;
/**
* A component of {@link Task} that represents a computation carried out by a single {@link Executor}.
*
* A {@link Task} consists of a number of {@link ExecutionUnit}.
*
* @since 1.FATTASK
*/
public interface ExecutionUnit extends ResourceActivity {
/**
* If this task needs to be run on a node with a particular label,
* return that {@link Label}. Otherwise null, indicating
* it can run on anywhere.
*/
Label getAssignedLabel();
/**
* If the previous execution of this task run on a certain node
* and this task prefers to run on the same node, return that.
* Otherwise null.
*/
Node getLastBuiltOn();
/**
* Estimate of how long will it take to execute this task.
* Measured in milliseconds.
*
* @return -1 if it's impossible to estimate.
*/
long getEstimatedDuration();
/**
* Creates {@link Executable}, which performs the actual execution of the task.
*/
Executable createExecutable() throws IOException;
/**
* If a subset of {@link ExecutionUnit}s of a {@link Task} needs to be collocated with other {@link ExecutionUnit}s,
* those {@link ExecutionUnit}s should return the equal object here. If null, the execution unit isn't under a
* colocation constraint.
*
* @since 1.FATTASK
*/
Object getSameNodeConstraint();
}
/*
* The MIT License
*
* Copyright (c) 2010, InfraDNA, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model.queue;
import hudson.model.Computer;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.Queue;
import hudson.model.Queue.JobOffer;
import hudson.model.Queue.Task;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author Kohsuke Kawaguchi
*/
public class MatchingWorksheet {
public final List<ExecutorChunk> executors;
public final List<WorkChunk> works;
// private final BitSet applicability = new BitSet();
private static class ReadOnlyList<E> extends AbstractList<E> {
protected final List<E> base;
ReadOnlyList(List<E> base) {
this.base = base;
}
public E get(int index) {
return base.get(index);
}
public int size() {
return base.size();
}
}
public final class ExecutorChunk extends ReadOnlyList<JobOffer> {
public final int index;
public final Computer computer;
public final Node node;
private ExecutorChunk(List<JobOffer> base, int index) {
super(base);
this.index = index;
assert base.size()>1;
computer = base.get(0).executor.getOwner();
node = computer.getNode();
}
public boolean canAccept(WorkChunk c) {
return this.size() > c.size()
&& (c.assignedLabel==null || c.assignedLabel.contains(node));
}
public String getName() {
return node.getNodeName();
}
/**
* Alias for size but more readable.
*/
public int capacity() {
return size();
}
private void execute(WorkChunk wc, WorkUnitContext wuc) {
assert capacity() > wc.size();
for (int i=0; i<wc.size(); i++)
get(i).set(wuc.createWorkUnit(wc.get(i)));
}
}
public class WorkChunk extends ReadOnlyList<ExecutionUnit> {
public final int index;
// the main should be always at position 0
// /**
// * This chunk includes {@linkplain WorkUnit#isMainWork() the main work unit}.
// */
// public final boolean isMain;
/**
* If this task needs to be run on a node with a particular label,
* return that {@link Label}. Otherwise null, indicating
* it can run on anywhere.
*/
public final Label assignedLabel;
/**
* If the previous execution of this task run on a certain node
* and this task prefers to run on the same node, return that.
* Otherwise null.
*/
public final ExecutorChunk lastBuiltOn;
private WorkChunk(List<ExecutionUnit> base, int index) {
super(base);
assert base.size()>1;
this.index = index;
this.assignedLabel = base.get(0).getAssignedLabel();
Node lbo = base.get(0).getLastBuiltOn();
for (ExecutorChunk ec : executors) {
if (ec.node==lbo) {
lastBuiltOn = ec;
return;
}
}
lastBuiltOn = null;
}
public List<ExecutorChunk> applicableExecutorChunks() {
List<ExecutorChunk> r = new ArrayList<ExecutorChunk>(executors.size());
for (ExecutorChunk e : executors) {
if (e.canAccept(this))
r.add(e);
}
return r;
}
}
public final class Mapping {
// for each WorkChunk, identify ExecutorChunk where it is assigned to.
private final ExecutorChunk[] mapping = new ExecutorChunk[works.size()];
public ExecutorChunk assigned(int index) {
return mapping[index];
}
public WorkChunk get(int index) {
return works.get(index);
}
public ExecutorChunk assign(int index, ExecutorChunk element) {
ExecutorChunk o = mapping[index];
mapping[index] = element;
return o;
}
public int size() {
return mapping.length;
}
public boolean isPartiallyValid() {
int[] used = new int[executors.size()];
for (int i=0; i<mapping.length; i++) {
ExecutorChunk ec = mapping[i];
if (ec==null) continue;
if (!ec.canAccept(works(i)))
return false; // invalid assignment
if ((used[ec.index] += works(i).size()) > ec.capacity())
return false;
}
return true;
}
public boolean isCompletelyValid() {
for (ExecutorChunk ec : mapping)
if (ec==null) return false; // unassigned
return isPartiallyValid();
}
/**
* Executes this mapping
*/
public void execute(WorkUnitContext wuc) {
if (!isCompletelyValid())
throw new IllegalStateException();
for (int i=0; i<size(); i++)
assigned(i).execute(get(i),wuc);
}
}
public MatchingWorksheet(Task task, List<JobOffer> offers) {
// executors
Map<Computer,List<JobOffer>> j = new HashMap<Computer, List<JobOffer>>();
for (JobOffer o : offers) {
Computer c = o.executor.getOwner();
List<Queue.JobOffer> l = j.get(c);
if (l==null)
j.put(c,l=new ArrayList<JobOffer>());
l.add(o);
}
// build into the final shape
List<ExecutorChunk> executors = new ArrayList<ExecutorChunk>();
for (List<JobOffer> group : j.values()) {
ExecutorChunk ec = new ExecutorChunk(group, executors.size());
if (ec.node==null) continue; // evict out of sync node
executors.add(ec);
}
this.executors = Collections.unmodifiableList(executors);
// group execution units into chunks. use of LinkedHashMap ensures that the main work comes at the top
Map<Object,List<ExecutionUnit>> m = new LinkedHashMap<Object,List<ExecutionUnit>>();
build(task,m);
for (ExecutionUnit meu : task.getMemberExecutionUnits())
build(meu,m);
// build into the final shape
List<WorkChunk> works = new ArrayList<WorkChunk>();
for (List<ExecutionUnit> group : m.values()) {
works.add(new WorkChunk(group,works.size()));
}
this.works = Collections.unmodifiableList(works);
}
private void build(ExecutionUnit eu, Map<Object, List<ExecutionUnit>> m) {
Object c = eu.getSameNodeConstraint();
if (c==null) c = new Object();
List<ExecutionUnit> l = m.get(c);
if (l==null)
m.put(c,l= new ArrayList<ExecutionUnit>());
l.add(eu);
}
public WorkChunk works(int index) {
return works.get(index);
}
public ExecutorChunk executors(int index) {
return executors.get(index);
}
//
// public void setIncompatible(int x, int u) {
// assert 0<=x && x<=executors.size();
// assert 0<=u && u<= works.size();
// applicability.set(x* works.size()+u);
// }
//
// public boolean isIncompatible(int x, int u) {
// assert 0<=x && x<=executors.size();
// assert 0<=u && u<= works.size();
// return applicability.get(x* works.size()+u);
// }
//
// /**
// * Creates a clone.
// */
// public MatchingWorksheet copy() {
// throw new UnsupportedOperationException();
// }
}
......@@ -28,7 +28,6 @@ import hudson.model.Label;
import hudson.model.Node;
import hudson.model.Queue;
import hudson.model.Queue.Executable;
import hudson.model.Queue.ExecutionUnit;
import hudson.model.Queue.Task;
import hudson.model.ResourceList;
......
......@@ -25,7 +25,6 @@ package hudson.model.queue;
import hudson.model.Executor;
import hudson.model.Queue;
import hudson.model.Queue.ExecutionUnit;
import hudson.model.Queue.Task;
/**
......
......@@ -26,8 +26,7 @@ package hudson.model.queue;
import hudson.model.Action;
import hudson.model.Executor;
import hudson.model.Queue;
import hudson.model.Queue.ExecutionUnit;
import hudson.model.Queue.Item;
import hudson.model.Queue.BuildableItem;
import hudson.model.Queue.Task;
import java.util.List;
......@@ -40,6 +39,8 @@ import java.util.List;
public final class WorkUnitContext {
private final int workUnitSize;
public final BuildableItem item;
public final Task task;
/**
......@@ -54,7 +55,8 @@ public final class WorkUnitContext {
private final Latch startLatch, endLatch;
public WorkUnitContext(Queue.Item item) {
public WorkUnitContext(BuildableItem item) {
this.item = item;
this.task = item.task;
this.future = (FutureImpl)item.getFuture();
this.actions = item.getActions();
......@@ -108,4 +110,9 @@ public final class WorkUnitContext {
}
}
}
public void abort() {
startLatch.abort();
endLatch.abort();
}
}
......@@ -35,7 +35,7 @@ http://developer.yahoo.net/yui/license.txt
* @param {String | HTMLElement} el Reference to the element that will be animated
* @param {Object} attributes The attribute(s) to be animated.
* Each attribute is an object with at minimum a "to" or "by" member defined.
* Additional optional members are "from" (defaults to current value), "units" (defaults to "px").
* Additional optional members are "from" (defaults to current value), "units" (defaults to "px").
* All attribute names use camelCase.
* @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
* @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
......@@ -178,7 +178,7 @@ Anim.prototype = {
this.runtimeAttributes[attr].start = start;
this.runtimeAttributes[attr].end = end;
// set units if needed
// set works if needed
this.runtimeAttributes[attr].unit = ( isset(attributes[attr].unit) ) ?
attributes[attr]['unit'] : this.getDefaultUnit(attr);
return true;
......@@ -190,7 +190,7 @@ Anim.prototype = {
* @param {String | HTMLElement} el Reference to the element that will be animated
* @param {Object} attributes The attribute(s) to be animated.
* Each attribute is an object with at minimum a "to" or "by" member defined.
* Additional optional members are "from" (defaults to current value), "units" (defaults to "px").
* Additional optional members are "from" (defaults to current value), "works" (defaults to "px").
* All attribute names use camelCase.
* @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
* @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
......@@ -234,7 +234,7 @@ Anim.prototype = {
* If "to" is supplied, the animation will end with the attribute at that value.
* If "by" is supplied, the animation will end at that value plus its starting value.
* If both are supplied, "to" is used, and "by" is ignored.
* Optional additional member include "from" (the value the attribute should start animating from, defaults to current value), and "unit" (the units to apply to the values).
* Optional additional member include "from" (the value the attribute should start animating from, defaults to current value), and "unit" (the works to apply to the values).
* @property attributes
* @type Object
*/
......@@ -699,7 +699,7 @@ YAHOO.util.Bezier = new function() {
* @param {HTMLElement | String} el Reference to the element that will be animated
* @param {Object} attributes The attribute(s) to be animated.
* Each attribute is an object with at minimum a "to" or "by" member defined.
* Additional optional members are "from" (defaults to current value), "units" (defaults to "px").
* Additional optional members are "from" (defaults to current value), "works" (defaults to "px").
* All attribute names use camelCase.
* @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
* @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
......@@ -1169,7 +1169,7 @@ YAHOO.util.Easing = {
* @param {String | HTMLElement} el Reference to the element that will be animated
* @param {Object} attributes The attribute(s) to be animated.
* Each attribute is an object with at minimum a "to" or "by" member defined.
* Additional optional members are "from" (defaults to current value), "units" (defaults to "px").
* Additional optional members are "from" (defaults to current value), "works" (defaults to "px").
* All attribute names use camelCase.
* @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
* @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
......@@ -1320,7 +1320,7 @@ YAHOO.util.Easing = {
* @param {String or HTMLElement} el Reference to the element that will be animated
* @param {Object} attributes The attribute(s) to be animated.
* Each attribute is an object with at minimum a "to" or "by" member defined.
* Additional optional members are "from" (defaults to current value), "units" (defaults to "px").
* Additional optional members are "from" (defaults to current value), "works" (defaults to "px").
* All attribute names use camelCase.
* @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
* @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册